View Javadoc
1   /*
2    * This file is part of Waarp Project (named also Waarp or GG).
3    *
4    *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5    *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
6    * individual contributors.
7    *
8    *  All Waarp Project is free software: you can redistribute it and/or
9    * modify it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or (at your
11   * option) any later version.
12   *
13   * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15   * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License along with
18   * Waarp . If not, see <http://www.gnu.org/licenses/>.
19   */
20  package org.waarp.common.file;
21  
22  import org.apache.commons.compress.compressors.CompressorInputStream;
23  import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
24  import org.waarp.common.command.exception.Reply550Exception;
25  import org.waarp.common.digest.FilesystemBasedDigest;
26  import org.waarp.common.logging.SysErrLogger;
27  
28  import java.io.File;
29  import java.io.FileInputStream;
30  import java.io.FileNotFoundException;
31  import java.io.FileOutputStream;
32  import java.io.FilenameFilter;
33  import java.io.IOException;
34  import java.io.InputStream;
35  import java.io.OutputStream;
36  import java.io.RandomAccessFile;
37  import java.io.Reader;
38  import java.io.Writer;
39  import java.util.ArrayList;
40  import java.util.Arrays;
41  
42  import static com.google.common.base.Preconditions.*;
43  
44  /**
45   * File Utils
46   */
47  public final class FileUtils {
48    public static final int ZERO_COPY_CHUNK_SIZE = 64 * 1024;
49  
50    private static final File[] FILE_0_LENGTH = new File[0];
51  
52    private FileUtils() {
53    }
54  
55    public static void close(final Reader stream) {
56      if (stream == null) {
57        return;
58      }
59      try {
60        stream.close();
61      } catch (final Exception ignored) {
62        SysErrLogger.FAKE_LOGGER.ignoreLog(ignored);
63      }
64    }
65  
66    public static void close(final Writer stream) {
67      if (stream == null) {
68        return;
69      }
70      try {
71        stream.flush();
72      } catch (final Exception ignored) {
73        SysErrLogger.FAKE_LOGGER.ignoreLog(ignored);
74      }
75      try {
76        stream.close();
77      } catch (final Exception ignored) {
78        SysErrLogger.FAKE_LOGGER.ignoreLog(ignored);
79      }
80    }
81  
82    public static void close(final InputStream stream) {
83      if (stream == null) {
84        return;
85      }
86      try {
87        stream.close();
88      } catch (final Exception ignored) {
89        SysErrLogger.FAKE_LOGGER.ignoreLog(ignored);
90      }
91    }
92  
93    public static void close(final OutputStream stream) {
94      if (stream == null) {
95        return;
96      }
97      try {
98        stream.flush();
99      } catch (final Exception ignored) {
100       SysErrLogger.FAKE_LOGGER.ignoreLog(ignored);
101     }
102     try {
103       stream.close();
104     } catch (final Exception ignored) {
105       SysErrLogger.FAKE_LOGGER.ignoreLog(ignored);
106     }
107   }
108 
109   public static void close(final RandomAccessFile accessFile) {
110     if (accessFile == null) {
111       return;
112     }
113     try {
114       accessFile.close();
115     } catch (final Exception ignored) {
116       SysErrLogger.FAKE_LOGGER.ignoreLog(ignored);
117     }
118   }
119 
120   /**
121    * Delete the directory associated with the File as path if empty
122    *
123    * @param directory
124    *
125    * @return True if deleted, False else.
126    */
127   public static boolean deleteDir(final File directory) {
128     if (directory == null) {
129       return true;
130     }
131     if (!directory.exists()) {
132       return true;
133     }
134     if (!directory.isDirectory()) {
135       return false;
136     }
137     return directory.delete();
138   }
139 
140   /**
141    * Delete physically the file
142    *
143    * @param file
144    *
145    * @return True if OK, else if not (or if the file never exists).
146    */
147   public static boolean delete(final File file) {
148     if (!file.exists()) {
149       return true;
150     }
151     return file.delete();
152   }
153 
154   /**
155    * Copy from a directory to a directory with recursivity
156    *
157    * @param from
158    * @param directoryTo
159    * @param move True if the copy is in fact a move operation
160    *
161    * @return the group of copy files or null (partially or totally) if an
162    *     error occurs
163    *
164    * @throws Reply550Exception
165    */
166   public static File[] copyRecursive(final File from, final File directoryTo,
167                                      final boolean move)
168       throws Reply550Exception {
169     if (from == null || directoryTo == null) {
170       return null;
171     }
172     final ArrayList<File> to = new ArrayList<File>();
173     if (createDir(directoryTo)) {
174       final File[] subfrom = from.listFiles();
175       if (subfrom != null) {
176         for (final File element : subfrom) {
177           if (element.isFile()) {
178             to.add(copyToDir(element, directoryTo, move));
179           } else {
180             final File newTo = new File(directoryTo, element.getName());
181             newTo.mkdirs(); //NOSONAR
182             final File[] copied = copyRecursive(element, newTo, move);
183             to.addAll(Arrays.asList(copied));
184           }
185         }
186       }
187     }
188     return to.toArray(FILE_0_LENGTH);
189   }
190 
191   /**
192    * Copy a group of files to a directory (will ignore directories)
193    *
194    * @param from
195    * @param directoryTo
196    * @param move True if the copy is in fact a move operation
197    *
198    * @return the group of copy files or null (partially or totally) if an
199    *     error occurs
200    *
201    * @throws Reply550Exception
202    */
203   public static File[] copy(final File[] from, final File directoryTo,
204                             final boolean move) throws Reply550Exception {
205     if (from == null || directoryTo == null) {
206       return null;
207     }
208     File[] to = null;
209     if (createDir(directoryTo)) {
210       to = new File[from.length];
211       for (int i = 0; i < from.length; i++) {
212         if (from[i].isFile()) {
213           to[i] = copyToDir(from[i], directoryTo, move);
214         }
215       }
216     }
217     return to;
218   }
219 
220   /**
221    * Create the directory associated with the File as path
222    *
223    * @param directory
224    *
225    * @return True if created, False else.
226    */
227   public static boolean createDir(final File directory) {
228     if (directory == null) {
229       return false;
230     }
231     if (directory.isDirectory()) {
232       return true;
233     }
234     return directory.mkdirs();
235   }
236 
237   /**
238    * Copy one file to a directory
239    *
240    * @param from
241    * @param directoryTo
242    * @param move True if the copy is in fact a move operation
243    *
244    * @return The copied file or null if an error occurs
245    *
246    * @throws Reply550Exception
247    */
248   private static File copyToDir(final File from, final File directoryTo,
249                                 final boolean move) throws Reply550Exception {
250     if (from == null || directoryTo == null) {
251       throw new Reply550Exception("Source or Destination is null");
252     }
253     if (!from.isFile()) {
254       throw new Reply550Exception("Source file is a directory");
255     }
256     if (createDir(directoryTo)) {
257       final File to = new File(directoryTo, from.getName());
258       copy(from, to, move, false);
259       return to;
260     }
261     throw new Reply550Exception("Cannot access to parent dir of destination");
262   }
263 
264   /**
265    * Copy one file to another one
266    *
267    * @param from
268    * @param to
269    * @param move True if the copy is in fact a move operation
270    * @param append True if the copy is in append
271    *
272    * @throws Reply550Exception
273    */
274   public static void copy(final File from, final File to, final boolean move,
275                           final boolean append) throws Reply550Exception {
276     if (from == null || to == null) {
277       throw new Reply550Exception("Source or Destination is null");
278     }
279     if (!from.isFile()) {
280       throw new Reply550Exception("Source file is a directory");
281     }
282     final File directoryTo = to.getParentFile();
283     if (createDir(directoryTo)) {
284       if (move && from.renameTo(to)) {
285         return;
286       }
287       FileInputStream inputStream = null;
288       FileOutputStream outputStream = null;
289       try {
290         try {
291           inputStream = new FileInputStream(from.getPath());
292         } catch (final FileNotFoundException e) {
293           throw new Reply550Exception("Cannot read source file");
294         }
295         try {
296           outputStream = new FileOutputStream(to.getPath(), append);
297         } catch (final FileNotFoundException e) {
298           throw new Reply550Exception("Cannot write destination file");
299         }
300         try {
301           copy(inputStream, outputStream);
302         } catch (final IOException e) {
303           throw new Reply550Exception("Cannot copy, e");
304         }
305       } finally {
306         close(outputStream);
307         close(inputStream);
308       }
309       if (move) {
310         // do not test the delete
311         from.delete(); // NOSONAR
312       }
313       return;
314     }
315     throw new Reply550Exception("Cannot access to parent dir of destination");
316   }
317 
318   /**
319    * Delete the directory and its subdirs associated with the File dir if
320    * empty
321    *
322    * @param dir
323    *
324    * @return True if deleted, False else.
325    */
326   private static boolean deleteRecursiveFileDir(final File dir) {
327     if (dir == null) {
328       return true;
329     }
330     boolean retour = true;
331     if (!dir.exists()) {
332       return true;
333     }
334     final File[] list = dir.listFiles();
335     if (list == null || list.length == 0) {
336       return dir.delete();
337     }
338     for (final File file : list) {
339       if (file.isDirectory()) {
340         if (!deleteRecursiveFileDir(file)) {
341           retour = false;
342         }
343       } else {
344         return false;
345       }
346     }
347     if (retour) {
348       retour = dir.delete();
349     }
350     return retour;
351   }
352 
353   /**
354    * Delete all
355    *
356    * @param directory
357    *
358    * @return True if all sub directories or files are deleted, False else.
359    */
360   private static boolean forceDeleteRecursiveSubDir(final File directory) {
361     if (directory == null) {
362       return true;
363     }
364     boolean retour = true;
365     if (!directory.exists()) {
366       return true;
367     }
368     if (!directory.isDirectory()) {
369       return directory.delete();
370     }
371     final File[] list = directory.listFiles();
372     if (list == null || list.length == 0) {
373       return true;
374     }
375     for (final File file : list) {
376       if (file.isDirectory()) {
377         if (!forceDeleteRecursiveSubDir(file)) {
378           retour = false;
379         }
380       } else {
381         if (!file.delete()) {
382           retour = false;
383         }
384       }
385     }
386     if (retour) {
387       retour = directory.delete();
388     }
389     return retour;
390   }
391 
392   /**
393    * Delete all its subdirs and files associated, not itself
394    *
395    * @param directory
396    *
397    * @return True if all sub directories or files are deleted, False else.
398    */
399   public static boolean forceDeleteRecursiveDir(final File directory) {
400     if (directory == null) {
401       return true;
402     }
403     boolean retour = true;
404     if (!directory.exists()) {
405       return true;
406     }
407     if (!directory.isDirectory()) {
408       return false;
409     }
410     final File[] list = directory.listFiles();
411     if (list == null || list.length == 0) {
412       return true;
413     }
414     for (final File file : list) {
415       if (file.isDirectory()) {
416         if (!forceDeleteRecursiveSubDir(file)) {
417           retour = false;
418         }
419       } else {
420         if (!file.delete()) {
421           retour = false;
422         }
423       }
424     }
425     return retour;
426   }
427 
428   /**
429    * Delete the directory and its subdirs associated with the File as path if
430    * empty
431    *
432    * @param directory
433    *
434    * @return True if deleted, False else.
435    */
436   public static boolean deleteRecursiveDir(final File directory) {
437     if (directory == null) {
438       return true;
439     }
440     boolean retour = true;
441     if (!directory.exists()) {
442       return true;
443     }
444     if (!directory.isDirectory()) {
445       return false;
446     }
447     final File[] list = directory.listFiles();
448     if (list == null || list.length == 0) {
449       retour = directory.delete();
450       return retour;
451     }
452     for (final File file : list) {
453       if (file.isDirectory()) {
454         if (!deleteRecursiveFileDir(file)) {
455           retour = false;
456         }
457       } else {
458         retour = false;
459       }
460     }
461     if (retour) {
462       retour = directory.delete();
463     }
464     return retour;
465   }
466 
467   /**
468    * @param fileName
469    * @param path
470    *
471    * @return true if the file exist in the specified path
472    */
473   public static boolean fileExist(final String fileName, final String path) {
474     boolean exist = false;
475     final String fileString = path + File.separator + fileName;
476     final File file = new File(fileString);
477     if (file.exists()) {
478       exist = true;
479     }
480     return exist;
481   }
482 
483   /**
484    * Get the list of files from a given directory
485    *
486    * @param directory
487    *
488    * @return the list of files (as an array)
489    */
490   public static File[] getFiles(final File directory) {
491     if (directory == null || !directory.isDirectory()) {
492       return FILE_0_LENGTH;
493     }
494     return directory.listFiles();
495   }
496 
497   /**
498    * Compute global hash (if possible) from a file but up to length
499    *
500    * @param digest
501    * @param file
502    * @param length
503    */
504   public static void computeGlobalHash(final FilesystemBasedDigest digest,
505                                        final File file, final int length) {
506     if (digest == null) {
507       return;
508     }
509     final byte[] bytes = new byte[ZERO_COPY_CHUNK_SIZE];
510     int still = length;
511     int len = Math.min(still, ZERO_COPY_CHUNK_SIZE);
512     FileInputStream inputStream = null;
513     try {
514       inputStream = new FileInputStream(file);
515       int read = 1;
516       while (read > 0) {
517         read = inputStream.read(bytes, 0, len);
518         if (read <= 0) {
519           break;
520         }
521         digest.Update(bytes, 0, read);
522         still -= read;
523         if (still <= 0) {
524           break;
525         }
526         len = Math.min(still, ZERO_COPY_CHUNK_SIZE);
527       }
528     } catch (final FileNotFoundException e) {
529       // error
530     } catch (final IOException e) {
531       // error
532     } finally {
533       close(inputStream);
534     }
535   }
536 
537   /**
538    * Get the list of files from a given directory and a filter
539    *
540    * @param directory
541    * @param filter
542    *
543    * @return the list of files (as an array)
544    */
545   public static File[] getFiles(final File directory,
546                                 final FilenameFilter filter) {
547     if (directory == null || !directory.isDirectory()) {
548       return FILE_0_LENGTH;
549     }
550     return directory.listFiles(filter);
551   }
552 
553   /**
554    * Read one compressed file and return the associated BufferedReader
555    *
556    * @param fileIn
557    * @param fileOut
558    *
559    * @return the size of the uncompressed file or -1 if an error occurs
560    */
561   public static long uncompressedBz2File(final File fileIn,
562                                          final File fileOut) {
563     FileInputStream fin = null;
564     CompressorInputStream input = null;
565     FileOutputStream output = null;
566     try {
567       fin = new FileInputStream(fileIn);
568       input = new BZip2CompressorInputStream(fin);
569       output = new FileOutputStream(fileOut);
570       return copy(input, output);
571     } catch (final IOException e) {
572       SysErrLogger.FAKE_LOGGER.syserr(e);
573       return -1;
574     } finally {
575       FileUtils.close(output);
576       FileUtils.close(input);
577       FileUtils.close(fin);
578     }
579   }
580 
581 
582   /**
583    * From Google Guava copy within ByteStreams
584    *
585    * Copies all bytes from the input stream to the output stream. Does not close or flush either
586    * stream.
587    *
588    * @param from the input stream to read from
589    * @param to the output stream to write to
590    *
591    * @return the number of bytes copied
592    *
593    * @throws IOException if an I/O error occurs
594    */
595   public static long copy(final InputStream from, final OutputStream to)
596       throws IOException {
597     checkNotNull(from);
598     checkNotNull(to);
599     final byte[] buf = new byte[ZERO_COPY_CHUNK_SIZE];
600     long total = 0;
601     while (true) {
602       final int r = from.read(buf);
603       if (r == -1) {
604         break;
605       }
606       to.write(buf, 0, r);
607       total += r;
608     }
609     return total;
610   }
611 
612   /**
613    * Helper method to write from InputStream to OutputStream,
614    * closing streams
615    *
616    * @param blockSize
617    * @param inputStream
618    * @param out
619    *
620    * @throws IOException
621    */
622   public static void copy(final int blockSize, final InputStream inputStream,
623                           final OutputStream out) throws IOException {
624     final byte[] buffer = new byte[blockSize];
625     while (true) {
626       final int r = inputStream.read(buffer);
627       if (r == -1) {
628         break;
629       }
630       out.write(buffer, 0, r);
631     }
632     out.flush();
633     close(out);
634   }
635 
636   /**
637    * Check if the file (directory or file) is in directory dir or in sub
638    * directories of dir
639    *
640    * @param dir
641    * @param file
642    *
643    * @return True if in (sub) directory (of) dir
644    */
645   public static boolean isInSubdirectory(final File dir, final File file) {
646     if (file == null || dir == null) {
647       return false;
648     }
649     return file.equals(dir) || isInSubdirectory(dir, file.getParentFile());
650   }
651 }