001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.file;
020
021import java.io.BufferedReader;
022import java.io.File;
023import java.io.FileFilter;
024import java.io.FileOutputStream;
025import java.io.IOException;
026import java.io.InputStreamReader;
027import java.io.UnsupportedEncodingException;
028import java.net.URLDecoder;
029import java.util.ArrayList;
030import java.util.List;
031
032import icy.network.NetworkUtil;
033import icy.system.IcyExceptionHandler;
034import icy.system.SystemUtil;
035import icy.system.thread.ThreadUtil;
036import icy.util.StringUtil;
037
038/**
039 * @author stephane
040 */
041public class FileUtil
042{
043    public static final char separatorChar = '/';
044    public static final String separator = "/";
045
046    public static final String APPLICATION_DIRECTORY = getApplicationDirectory();
047
048    /**
049     * Cleanup the file path (replace some problematic character by "_")
050     */
051    public static String cleanPath(String filePath)
052    {
053        String result = filePath;
054
055        if (result != null)
056        {
057            // remove ':' other than for drive separation
058            if (result.length() >= 2)
059                result = result.substring(0, 2) + result.substring(2).replaceAll(":", "_");
060            // remove '!' characters
061            result = result.replaceAll("!", "_");
062            // remove '#' characters
063            result = result.replaceAll("#", "_");
064        }
065
066        return result;
067    }
068
069    /**
070     * Transform any system specific path in java generic path form.<br>
071     * Ex: "C:\windows" --> "C:/windows"
072     */
073    public static String getGenericPath(String path)
074    {
075        if (path != null)
076            return path.replace('\\', '/');
077
078        return null;
079    }
080
081    /**
082     * Returns default temporary directory.
083     * ex:<br>
084     * <code>c:/temp</code><br>
085     * <code>/tmp</code><br>
086     * Same as {@link SystemUtil#getTempDirectory()}
087     */
088    public static String getTempDirectory()
089    {
090        final String result = FileUtil.getGenericPath(SystemUtil.getProperty("java.io.tmpdir"));
091        final int len = result.length();
092
093        // remove last separator
094        if ((len > 1) && (result.charAt(len - 1) == FileUtil.separatorChar))
095            return result.substring(0, len - 1);
096
097        return result;
098    }
099
100    /**
101     * Change path extension.<br>
102     * Ex : setExtension(path, ".dat")<br>
103     * "c:\temp" --> "c:\temp.dat"
104     * "c:\file.out" --> "c:\file.dat"
105     * "" --> ""
106     */
107    public static String setExtension(String path, String extension)
108    {
109        final String finalPath = getGenericPath(path);
110
111        if (StringUtil.isEmpty(finalPath))
112            return "";
113
114        final int len = finalPath.length();
115        String result = finalPath;
116
117        final int dotIndex = result.lastIndexOf(".");
118        // ensure we are modifying an extension
119        if (dotIndex >= 0 && (len - dotIndex) <= 5)
120        {
121            // we consider that an extension starting with a digit is not an extension
122            if (((dotIndex + 1) == len) || !Character.isDigit(result.charAt(dotIndex + 1)))
123                result = result.substring(0, dotIndex);
124        }
125
126        if (extension != null)
127            result += extension;
128
129        return result;
130    }
131
132    public static void ensureParentDirExist(String filename)
133    {
134        ensureParentDirExist(new File(getGenericPath(filename)));
135    }
136
137    public static boolean ensureParentDirExist(File file)
138    {
139        final String dir = file.getParent();
140
141        if (dir != null)
142            return createDir(dir);
143
144        return true;
145    }
146
147    public static boolean createDir(String dirname)
148    {
149        return createDir(new File(getGenericPath(dirname)));
150    }
151
152    public static boolean createDir(File dir)
153    {
154        if (!dir.exists())
155            return dir.mkdirs();
156
157        return true;
158    }
159
160    public static File createFile(String filename)
161    {
162        return createFile(new File(getGenericPath(filename)));
163    }
164
165    public static File createFile(File file)
166    {
167        if (!file.exists())
168        {
169            // create parent directory if not exist
170            ensureParentDirExist(file);
171
172            try
173            {
174                file.createNewFile();
175            }
176            catch (Exception e)
177            {
178                System.err.println("Error: can't create file '" + file.getAbsolutePath() + "':");
179                IcyExceptionHandler.showErrorMessage(e, false);
180                return null;
181            }
182        }
183
184        return file;
185    }
186
187    /**
188     * Transform the specified list of path to file.
189     */
190    public static File[] toFiles(String[] paths)
191    {
192        final File[] result = new File[paths.length];
193
194        for (int i = 0; i < paths.length; i++)
195            result[i] = new File(paths[i]);
196
197        return result;
198    }
199
200    /**
201     * Transform the specified list of path to file.
202     */
203    public static List<File> toFiles(List<String> paths)
204    {
205        final List<File> result = new ArrayList<File>(paths.size());
206
207        for (String path : paths)
208            result.add(new File(path));
209
210        return result;
211    }
212
213    /**
214     * Transform the specified list of file to path.
215     */
216    public static String[] toPaths(File[] files)
217    {
218        final String[] result = new String[files.length];
219
220        for (int i = 0; i < files.length; i++)
221            result[i] = getGenericPath(files[i].getAbsolutePath());
222
223        return result;
224    }
225
226    /**
227     * Transform the specified list of file to path.
228     */
229    public static List<String> toPaths(List<File> files)
230    {
231        final List<String> result = new ArrayList<String>(files.size());
232
233        for (File file : files)
234            result.add(getGenericPath(file.getAbsolutePath()));
235
236        return result;
237    }
238
239    /**
240     * Create a symbolic link file
241     */
242    public static boolean createLink(String path, String target)
243    {
244        final String finalPath = getGenericPath(path);
245
246        ensureParentDirExist(finalPath);
247
248        // use OS dependent command (FIXME : replace by java 7 API when available)
249        if (SystemUtil.isLinkSupported())
250        {
251            final Process process = SystemUtil.exec("ln -s " + target + " " + finalPath);
252
253            // error while executing command
254            if (process == null)
255                return false;
256
257            try
258            {
259                return (process.waitFor() == 0);
260            }
261            catch (InterruptedException e)
262            {
263                System.err.println("FileUtil.createLink(" + path + ", " + target + ") error :");
264                IcyExceptionHandler.showErrorMessage(e, false);
265                return false;
266            }
267        }
268
269        // use classic copy if link isn't supported by OS
270        return copy(target, finalPath, true, false, false);
271    }
272
273    public static byte[] load(String path, boolean displayError)
274    {
275        return load(new File(getGenericPath(path)), displayError);
276    }
277
278    public static byte[] load(File file, boolean displayError)
279    {
280        return NetworkUtil.download(file, null, displayError);
281    }
282
283    public static boolean save(String path, byte[] data, boolean displayError)
284    {
285        return save(new File(getGenericPath(path)), data, displayError);
286    }
287
288    public static boolean save(File file, byte[] data, boolean displayError)
289    {
290        final File f = createFile(file);
291
292        if (f != null)
293        {
294            try
295            {
296                final FileOutputStream out = new FileOutputStream(f);
297
298                out.write(data, 0, data.length);
299                out.close();
300            }
301            catch (Exception e)
302            {
303                if (displayError)
304                    System.err.println(e.getMessage());
305                // delete incorrect file
306                f.delete();
307                return false;
308            }
309
310            return true;
311        }
312
313        return false;
314    }
315
316    /**
317     * Returns the path where the application is located (current directory).<br>
318     * Ex: "D:/Apps/Icy"
319     */
320    public static String getApplicationDirectory()
321    {
322        String result;
323
324        try
325        {
326            // try to get from sources
327            final File f = new File(FileUtil.class.getProtectionDomain().getCodeSource().getLocation().toURI());
328
329            // if already a folder, return it directly
330            if (f.isDirectory())
331                result = f.getAbsolutePath();
332            else
333                result = f.getParentFile().getAbsolutePath();
334        }
335        catch (Exception e1)
336        {
337            try
338            {
339                // try to get from resource (this sometime return incorrect folder on mac osx)
340                result = new File(ClassLoader.getSystemClassLoader().getResource(".").toURI()).getAbsolutePath();
341            }
342            catch (Exception e2)
343            {
344                // use launch directory (which may be different from application directory)
345                result = new File(System.getProperty("user.dir")).getAbsolutePath();
346            }
347        }
348
349        try
350        {
351            // so we replace any %20 sequence in space
352            result = URLDecoder.decode(result, "UTF-8");
353        }
354        catch (UnsupportedEncodingException e)
355        {
356            // ignore
357        }
358
359        return getGenericPath(result);
360    }
361
362    /**
363     * @deprecated Use {@link #getApplicationDirectory()} instead.
364     */
365    @Deprecated
366    public static String getCurrentDirectory()
367    {
368        return getApplicationDirectory();
369    }
370
371    /**
372     * Return drive / mount point from specified path<br>
373     * <br>
374     * getDrive("D:/temp/file.txt") --> "D:"<br>
375     * getDrive("D:/temp") --> "D:"<br>
376     * getDrive("C:file.txt") --> "C:"<br>
377     * getDrive("file.txt") --> ""<br>
378     */
379    public static String getDrive(String path)
380    {
381        File fp = new File(path);
382        File f;
383        do
384        {
385            f = fp;
386            fp = f.getParentFile();
387        }
388        while (fp != null);
389
390        return f.getAbsolutePath();
391    }
392    
393    /**
394     * Return directory information from specified path<br>
395     * <br>
396     * getDirectory("/file.txt") --> "/"<br>
397     * getDirectory("D:/temp/file.txt") --> "D:/temp/"<br>
398     * getDirectory("D:/temp/") --> "D:/temp/"<br>
399     * getDirectory("D:/temp") --> "D:/"<br>
400     * getDirectory("C:file.txt") --> "C:"<br>
401     * getDirectory("file.txt") --> ""<br>
402     * getDirectory("file") --> ""<br>
403     * getDirectory(null) --> ""
404     */
405    public static String getDirectory(String path, boolean separator)
406    {
407        final String finalPath = getGenericPath(path);
408
409        if (!StringUtil.isEmpty(finalPath))
410        {
411            int index = finalPath.lastIndexOf(FileUtil.separatorChar);
412            if (index != -1)
413                return finalPath.substring(0, index + (separator ? 1 : 0));
414
415            index = finalPath.lastIndexOf(':');
416            if (index != -1)
417                return finalPath.substring(0, index + 1);
418        }
419
420        return "";
421    }
422
423    /**
424     * Return directory information from specified path<br>
425     * <br>
426     * getDirectory("/file.txt") --> "/"<br>
427     * getDirectory("D:/temp/file.txt") --> "D:/temp/"<br>
428     * getDirectory("D:/temp/") --> "D:/temp/"<br>
429     * getDirectory("D:/temp") --> "D:/"<br>
430     * getDirectory("C:file.txt") --> "C:"<br>
431     * getDirectory("file.txt") --> ""<br>
432     * getDirectory("file") --> ""<br>
433     * getDirectory(null) --> ""
434     */
435    public static String getDirectory(String path)
436    {
437        return getDirectory(path, true);
438    }
439
440    /**
441     * Return filename information from specified path.<br>
442     * <br>
443     * getFileName("/file.txt") --> "file.txt"<br>
444     * getFileName("D:/temp/file.txt") --> "file.txt"<br>
445     * getFileName("C:file.txt") --> "file.txt"<br>
446     * getFileName("file.txt") --> "file.txt"<br>
447     * getFileName(null) --> ""
448     */
449    public static String getFileName(String path)
450    {
451        return getFileName(path, true);
452    }
453
454    /**
455     * Return filename information from specified path.<br>
456     * Filename's extension is returned depending the withExtension flag value<br>
457     * <br>
458     * getFileName("/file.txt") --> "file(.txt)"<br>
459     * getFileName("D:/temp/file.txt") --> "file(.txt)"<br>
460     * getFileName("C:file.txt") --> "file(.txt)"<br>
461     * getFileName("file.txt") --> "file(.txt)"<br>
462     * getFileName(null) --> ""
463     */
464    public static String getFileName(String path, boolean withExtension)
465    {
466        final String finalPath = getGenericPath(path);
467
468        if (StringUtil.isEmpty(finalPath))
469            return "";
470
471        int index = finalPath.lastIndexOf(FileUtil.separatorChar);
472        final String fileName;
473
474        if (index != -1)
475            fileName = finalPath.substring(index + 1);
476        else
477        {
478            index = finalPath.lastIndexOf(':');
479
480            if (index != -1)
481                fileName = finalPath.substring(index + 1);
482            else
483                fileName = finalPath;
484        }
485
486        if (withExtension)
487            return fileName;
488
489        index = fileName.lastIndexOf('.');
490
491        if (index == 0)
492            return "";
493        else if (index != -1)
494            return fileName.substring(0, index);
495        else
496            return fileName;
497    }
498
499    /**
500     * Return filename extension information from specified path<br>
501     * Dot character is returned depending the withDot flag value<br>
502     * <br>
503     * getFileExtension("/file.txt") --> "(.)txt)"<br>
504     * getFileExtension("D:/temp/file.txt.old") --> "(.)old"<br>
505     * getFileExtension("C:/win/dir2/file") --> ""<br>
506     * getFileExtension(".txt") --> "(.)txt)"<br>
507     * getFileExtension(null) --> ""
508     */
509    public static String getFileExtension(String path, boolean withDot)
510    {
511        final String finalPath = getGenericPath(path);
512
513        if (StringUtil.isEmpty(finalPath))
514            return "";
515
516        final int indexSep = finalPath.lastIndexOf(separatorChar);
517        final int indexDot = finalPath.lastIndexOf('.');
518
519        if ((indexDot == -1) || (indexDot < indexSep))
520            return "";
521
522        if (withDot)
523            return finalPath.substring(indexDot);
524
525        return finalPath.substring(indexDot + 1);
526    }
527
528    /**
529     * Rename the specified <code>src</code> file to <code>dst</code> file.
530     * Return false if the method failed.
531     * 
532     * @param src
533     *        the source filename we want to rename from.
534     * @param dst
535     *        the destination filename we want to rename to.
536     * @param force
537     *        If set to <code>true</code> the destination file is overwritten if it was already
538     *        existing.
539     * @see File#renameTo(File)
540     */
541    public static boolean rename(String src, String dst, boolean force)
542    {
543        return rename(new File(getGenericPath(src)), new File(getGenericPath(dst)), force);
544    }
545
546    /**
547     * @deprecated Use {@link #rename(String, String, boolean)} instead
548     */
549    @Deprecated
550    public static boolean rename(String src, String dst, boolean force, boolean wantHidden)
551    {
552        return rename(src, dst, force);
553    }
554
555    /**
556     * Rename the specified 'src' file to 'dst' file.
557     * Return false if the method failed.
558     * 
559     * @see File#renameTo(File)
560     */
561    public static boolean rename(File src, File dst, boolean force)
562    {
563        if (src.exists())
564        {
565            if (dst.exists())
566            {
567                if (force)
568                {
569                    if (!delete(dst, true))
570                    {
571                        System.err.println(
572                                "Cannot rename '" + src.getAbsolutePath() + "' to '" + dst.getAbsolutePath() + "'");
573                        System.err.println("Reason: destination cannot be overwritten.");
574                        System.err.println("Make sure it is not locked by another program (e.g. Eclipse)");
575                        System.err.println("Also check that you have the rights to do this operation.");
576                        return false;
577                    }
578                }
579                else
580                {
581                    System.err.println(
582                            "Cannot rename '" + src.getAbsolutePath() + "' to '" + dst.getAbsolutePath() + "'");
583                    System.err.println("The destination already exists.");
584                    System.err.println("Use the 'force' flag to force the operation.");
585                    return false;
586                }
587            }
588
589            // create parent directory if not exist
590            ensureParentDirExist(dst);
591
592            // we can need that first to rename file
593            if (!src.setWritable(true, false))
594                src.setWritable(true, true);
595
596            final long start = System.currentTimeMillis();
597
598            // renameTo is not very reliable, better to do several try
599            boolean done = src.renameTo(dst);
600
601            while (!done && (System.currentTimeMillis() - start) < (10 * 1000))
602            {
603                // try to release objects which maintain lock
604                System.gc();
605                ThreadUtil.sleep(1000);
606
607                // may help
608                if (!src.setWritable(true, false))
609                    src.setWritable(true, true);
610
611                // retry
612                done = src.renameTo(dst);
613            }
614
615            if (!done)
616            {
617                System.err.println("Cannot rename '" + src.getAbsolutePath() + "' to '" + dst.getAbsolutePath() + "'");
618                System.err.println("Check that the source file is not locked.");
619                return false;
620            }
621
622            return true;
623        }
624
625        // missing input file
626        System.err.println("Cannot rename '" + src.getAbsolutePath() + "' to '" + dst.getAbsolutePath() + "'");
627        System.err.println("Input file '" + src.getAbsolutePath() + "' not found !");
628
629        return false;
630    }
631
632    /**
633     * @deprecated Use {@link #rename(File, File, boolean)} instead
634     */
635    @Deprecated
636    public static boolean rename(File src, File dst, boolean force, boolean wantHidden)
637    {
638        return rename(src, dst, force);
639    }
640
641    /**
642     * @deprecated Use {@link #rename(String, String, boolean)} instead
643     */
644    @Deprecated
645    public static boolean move(String src, String dst, boolean force)
646    {
647        return rename(src, dst, force);
648    }
649
650    /**
651     * @deprecated Use {@link #move(String, String, boolean)} instead
652     */
653    @Deprecated
654    public static boolean move(String src, String dst, boolean force, boolean wantHidden)
655    {
656        return move(src, dst, force);
657    }
658
659    /**
660     * @deprecated Use {@link #rename(File, File, boolean)} instead
661     */
662    @Deprecated
663    public static boolean move(File src, File dst, boolean force)
664    {
665        return rename(src, dst, force);
666    }
667
668    /**
669     * @deprecated Use {@link #move(File, File, boolean)} instead
670     */
671    @Deprecated
672    public static boolean move(File src, File dst, boolean force, boolean wantHidden)
673    {
674        return move(src, dst, force);
675    }
676
677    /**
678     * Copy src to dst.<br>
679     * Return true if file(s) successfully copied, false otherwise.
680     * 
681     * @param src
682     *        source file or directory
683     * @param dst
684     *        destination file or directory
685     * @param force
686     *        force copy to previous existing file
687     * @param recursive
688     *        also copy sub directory
689     * @return boolean
690     */
691    public static boolean copy(String src, String dst, boolean force, boolean recursive)
692    {
693        return copy(new File(getGenericPath(src)), new File(getGenericPath(dst)), force, recursive);
694    }
695
696    /**
697     * @deprecated Use {@link #copy(String, String, boolean, boolean)} instead
698     */
699    @Deprecated
700    public static boolean copy(String src, String dst, boolean force, boolean wantHidden, boolean recursive)
701    {
702        return copy(src, dst, force, recursive);
703    }
704
705    /**
706     * Copy src to dst with the specified parameters.<br>
707     * Return true if the operation succeed considering the specified parameters.<br>
708     * That means if you try to copy a hidden file with wantHidden set to false then true is
709     * returned<br>
710     * even if the file is not copied.
711     * 
712     * @param src
713     *        source file or directory
714     * @param dst
715     *        destination file or directory
716     * @param force
717     *        force copy to previous existing file
718     * @param recursive
719     *        also copy sub directory
720     * @return boolean
721     */
722    public static boolean copy(File src, File dst, boolean force, boolean recursive)
723    {
724        return copy_(src, dst, force, recursive, false);
725    }
726
727    /**
728     * @deprecated Use {@link #copy(File, File, boolean, boolean)} instead
729     */
730    @Deprecated
731    public static boolean copy(File src, File dst, boolean force, boolean wantHidden, boolean recursive)
732    {
733        return copy(src, dst, force, recursive);
734    }
735
736    /**
737     * internal copy
738     */
739    private static boolean copy_(File src, File dst, boolean force, boolean recursive, boolean inRecurse)
740    {
741        // directory copy ?
742        if (src.isDirectory())
743        {
744            // no recursive copy --> end
745            if (inRecurse && !recursive)
746                return true;
747
748            // so dst specify a directory too
749            createDir(dst);
750
751            boolean result = true;
752            // get files list
753            final String files[] = src.list();
754            // recursive copy
755            for (int i = 0; i < files.length; i++)
756                result = result & copy_(new File(src, files[i]), new File(dst, files[i]), force, recursive, true);
757
758            return result;
759        }
760
761        // single file copy
762        if (src.exists())
763        {
764            // destination already exist ?
765            if (dst.exists())
766            {
767                // copy only if force flag == true
768                if (force)
769                {
770                    if (!delete(dst, true))
771                    {
772                        System.err.println(
773                                "Cannot copy '" + src.getAbsolutePath() + "' to '" + dst.getAbsolutePath() + "'");
774                        System.err.println("Reason : destination cannot be overwritten.");
775                        System.err.println("Make sure it is not locked by another program (e.g. Eclipse)");
776                        System.err.println("Also check that you have the rights to do this operation.");
777                        return false;
778                    }
779                }
780                else
781                {
782                    System.err
783                            .println("Cannot copy '" + src.getAbsolutePath() + "' to '" + dst.getAbsolutePath() + "'");
784                    System.err.println("The destination already exists.");
785                    System.err.println("Use the 'force' flag to force file copy.");
786                    return false;
787                }
788            }
789
790            boolean lnk;
791
792            try
793            {
794                lnk = isLink(src);
795            }
796            catch (IOException e)
797            {
798                lnk = false;
799            }
800
801            // link file and link supported by OS ?
802            if (lnk && SystemUtil.isLinkSupported())
803            {
804                // use OS dependent command (FIXME : replace by java 7 API when available)
805                final Process process = SystemUtil.exec("cp -pRP " + src.getPath() + " " + dst.getPath());
806                int res = 1;
807
808                if (process != null)
809                {
810                    try
811                    {
812                        res = process.waitFor();
813                    }
814                    catch (InterruptedException e1)
815                    {
816                        // ignore;
817                    }
818                }
819
820                // error while executing command
821                if ((res != 0))
822                {
823                    System.err.println("FileUtil.copy(...) error while creating link '" + src.getPath() + "' to '"
824                            + dst.getPath() + "'");
825
826                    if (process != null)
827                    {
828                        // get error output and redirect it
829                        final BufferedReader stderr = new BufferedReader(
830                                new InputStreamReader(process.getErrorStream()));
831
832                        try
833                        {
834                            System.err.println(stderr.readLine());
835                            if (stderr.ready())
836                                System.err.println(stderr.readLine());
837                        }
838                        catch (IOException e)
839                        {
840                            // ignore
841                        }
842                    }
843                    else if (res == 1)
844                        System.err.println("Process interrupted.");
845
846                    return false;
847                }
848
849                return true;
850            }
851
852            // get data to copy from src
853            final byte[] data = load(src, true);
854            // source data correctly loaded
855            if (data != null)
856            {
857                // save in dst
858                if (save(dst, data, true))
859                {
860                    // and set the last modified info.
861                    dst.setLastModified(src.lastModified());
862                    return true;
863                }
864
865                return false;
866            }
867
868            // cannot load input file
869            System.err.println("Cannot copy '" + src.getAbsolutePath() + "' to '" + dst.getAbsolutePath() + "'");
870            System.err.println("Input file '" + src.getAbsolutePath() + "' data cannot be loaded !");
871
872            return false;
873        }
874
875        // missing input file
876        System.err.println("Cannot copy '" + src.getAbsolutePath() + "' to '" + dst.getAbsolutePath() + "'");
877        System.err.println("Input file '" + src.getAbsolutePath() + "' not found !");
878
879        return false;
880    }
881
882    /**
883     * Backup the specified file.<br>
884     * Basically create a .bak version of the file. If the backup file already exist a postfix
885     * number is automatically added.
886     * 
887     * @param filename
888     *        file to backup
889     * @return the backup filename is the operation success else return <code>null</code>
890     */
891    public static String backup(String filename)
892    {
893        int postfix = 0;
894        String backupName = filename + ".bak";
895
896        while (exists(backupName))
897        {
898            backupName = filename + "_" + StringUtil.toString(postfix, 3) + ".bak";
899            postfix++;
900        }
901
902        if (FileUtil.copy(filename, backupName, true, false))
903            return backupName;
904
905        return null;
906    }
907
908    /**
909     * Transform all directory entries by their sub files list
910     */
911    public static File[] explode(File[] files, FileFilter filter, boolean recursive, boolean wantHidden)
912    {
913        final List<File> result = new ArrayList<File>();
914
915        for (File file : files)
916        {
917            if (file.isDirectory())
918                getFiles(file, filter, recursive, true, false, wantHidden, result);
919            else
920                result.add(file);
921        }
922
923        return result.toArray(new File[result.size()]);
924    }
925
926    /**
927     * @deprecated Use {@link #explode(List, FileFilter, boolean, boolean)} instead.
928     */
929    @Deprecated
930    public static List<File> explode(List<File> files, boolean recursive, boolean wantHidden)
931    {
932        return explode(files, null, recursive, wantHidden);
933    }
934
935    /**
936     * Transform all directory entries by their sub files list
937     */
938    public static List<File> explode(List<File> files, FileFilter filter, boolean recursive, boolean wantHidden)
939    {
940        final List<File> result = new ArrayList<File>();
941
942        for (File file : files)
943        {
944            if (file.isDirectory())
945                getFiles(file, filter, recursive, true, false, wantHidden, result);
946            else
947                result.add(file);
948        }
949
950        return result;
951    }
952
953    /**
954     * Get file list from specified directory applying the specified parameters.
955     * 
956     * @param extension
957     *        the wanted extension file (without the dot character).<br>
958     *        Set it to <code>null</code> to accept all files.<br>
959     *        If you only want files without extension then use an empty String extension.
960     */
961    private static void getFiles(File folder, String extension, boolean ignoreExtensionCase, boolean recursive,
962            List<File> list)
963    {
964        final File[] files = folder.listFiles();
965
966        if (files != null)
967        {
968            for (File file : files)
969            {
970                if (file.isDirectory())
971                {
972                    if (recursive)
973                        getFiles(file, extension, ignoreExtensionCase, recursive, list);
974                }
975                else if (extension != null)
976                {
977                    final String fileExt = getFileExtension(file.getAbsolutePath(), false);
978
979                    if (ignoreExtensionCase)
980                    {
981                        if (extension.equalsIgnoreCase(fileExt))
982                            list.add(file);
983                    }
984                    else
985                    {
986                        if (extension.equals(fileExt))
987                            list.add(file);
988                    }
989                }
990                else
991                    list.add(file);
992            }
993        }
994    }
995
996    /**
997     * Get file list from specified folder applying the specified parameters.
998     */
999    public static File[] getFiles(File folder, String extension, boolean ignoreExtensionCase, boolean recursive)
1000    {
1001        final List<File> result = new ArrayList<File>();
1002
1003        getFiles(folder, extension, ignoreExtensionCase, recursive, result);
1004
1005        return result.toArray(new File[result.size()]);
1006    }
1007
1008    /**
1009     * Get file list from specified folder applying the specified parameters.
1010     */
1011    public static String[] getFiles(String folder, String extension, boolean ignoreExtensionCase, boolean recursive)
1012    {
1013        final File[] files = getFiles(new File(getGenericPath(folder)), extension, ignoreExtensionCase, recursive);
1014        final String[] result = new String[files.length];
1015
1016        for (int i = 0; i < files.length; i++)
1017            result[i] = files[i].getPath();
1018
1019        return result;
1020    }
1021
1022    /**
1023     * Get file list from specified directory applying the specified parameters.
1024     */
1025    private static void getFiles(File f, FileFilter filter, boolean recursive, boolean wantFile, boolean wantDirectory,
1026            boolean wantHidden, List<File> list)
1027    {
1028        final File[] files = f.listFiles(filter);
1029
1030        if (files != null)
1031        {
1032            for (File file : files)
1033            {
1034                if ((!file.isHidden()) || wantHidden)
1035                {
1036                    if (file.isDirectory())
1037                    {
1038                        if (wantDirectory)
1039                            list.add(file);
1040                        if (recursive)
1041                            getFiles(file, filter, recursive, wantFile, wantDirectory, wantHidden, list);
1042                    }
1043                    else if (wantFile)
1044                        list.add(file);
1045                }
1046            }
1047        }
1048    }
1049
1050    /**
1051     * Get directory list from specified directory applying the specified parameters
1052     */
1053    public static File[] getDirectories(File file, FileFilter filter, boolean recursive, boolean wantHidden)
1054    {
1055        final List<File> result = new ArrayList<File>();
1056
1057        getFiles(file, filter, recursive, false, true, wantHidden, result);
1058
1059        return result.toArray(new File[result.size()]);
1060    }
1061
1062    /**
1063     * Returns an array of file denoting content of specified directory and parameters.
1064     * 
1065     * @param directory
1066     *        The directory we want to retrieve content.<br>
1067     * @param filter
1068     *        A file filter.<br>
1069     *        If the given <code>filter</code> is <code>null</code> then all pathnames are accepted.
1070     *        Otherwise, a pathname satisfies the filter if and only if the value <code>true</code> results when the
1071     *        <code>{@link FileFilter#accept(java.io.File)}</code> method of the
1072     *        filter is invoked on the pathname.
1073     * @param recursive
1074     *        If <code>true</code> then content from sub folder is also returned.
1075     * @param wantDirectory
1076     *        If <code>true</code> then directory entries are also returned.
1077     * @param wantHidden
1078     *        If <code>true</code> then file or directory with <code>hidden</code> attribute are
1079     *        also returned.
1080     * @see File#listFiles(FileFilter)
1081     */
1082    public static File[] getFiles(File directory, FileFilter filter, boolean recursive, boolean wantDirectory,
1083            boolean wantHidden)
1084    {
1085        final List<File> result = new ArrayList<File>();
1086
1087        getFiles(directory, filter, recursive, true, wantDirectory, wantHidden, result);
1088
1089        return result.toArray(new File[result.size()]);
1090    }
1091
1092    /**
1093     * Returns an array of file path denoting content of specified directory and parameters
1094     * (String format).
1095     * 
1096     * @param directory
1097     *        The directory we want to retrieve content.<br>
1098     * @param filter
1099     *        A file filter.<br>
1100     *        If the given <code>filter</code> is <code>null</code> then all files are accepted.
1101     *        Otherwise, a file satisfies the filter if and only if the value <code>true</code> results when the
1102     *        <code>{@link FileFilter#accept(java.io.File)}</code> method of the
1103     *        filter is invoked on the pathname.
1104     * @param recursive
1105     *        If <code>true</code> then content from sub folder is also returned.
1106     * @param wantDirectory
1107     *        If <code>true</code> then directory entries are also returned.
1108     * @param wantHidden
1109     *        If <code>true</code> then file or directory with <code>hidden</code> attribute are
1110     *        also returned.
1111     */
1112    public static String[] getFiles(String directory, FileFilter filter, boolean recursive, boolean wantDirectory,
1113            boolean wantHidden)
1114    {
1115        final File[] files = getFiles(new File(getGenericPath(directory)), filter, recursive, wantDirectory,
1116                wantHidden);
1117        final String[] result = new String[files.length];
1118
1119        for (int i = 0; i < files.length; i++)
1120            result[i] = files[i].getPath();
1121
1122        return result;
1123    }
1124
1125    /**
1126     * @deprecated Use {@link #getFiles(File, FileFilter, boolean, boolean, boolean)} instead.
1127     */
1128    @Deprecated
1129    public static ArrayList<File> getFileList(String path, FileFilter filter, boolean recursive, boolean wantDirectory,
1130            boolean wantHidden)
1131    {
1132        final ArrayList<File> result = new ArrayList<File>();
1133
1134        getFiles(new File(getGenericPath(path)), filter, recursive, true, wantDirectory, wantHidden, result);
1135
1136        return result;
1137    }
1138
1139    /**
1140     * @deprecated Use {@link #getFiles(File, FileFilter, boolean, boolean, boolean)} instead.
1141     */
1142    @Deprecated
1143    public static ArrayList<File> getFileList(File file, FileFilter filter, boolean recursive, boolean wantDirectory,
1144            boolean wantHidden)
1145    {
1146        final ArrayList<File> result = new ArrayList<File>();
1147
1148        getFiles(file, filter, recursive, true, wantDirectory, wantHidden, result);
1149
1150        return result;
1151    }
1152
1153    /**
1154     * @deprecated Use {@link #getFiles(File, FileFilter, boolean, boolean, boolean)} instead.
1155     */
1156    @Deprecated
1157    public static ArrayList<File> getFileList(String path, FileFilter filter, boolean recursive, boolean wantHidden)
1158    {
1159        return getFileList(path, filter, recursive, false, wantHidden);
1160    }
1161
1162    /**
1163     * @deprecated Use {@link #getFiles(File, FileFilter, boolean, boolean, boolean)} instead.
1164     */
1165    @Deprecated
1166    public static ArrayList<File> getFileList(File file, FileFilter filter, boolean recursive, boolean wantHidden)
1167    {
1168        return getFileList(file, filter, recursive, false, wantHidden);
1169    }
1170
1171    /**
1172     * @deprecated Use {@link #getFiles(File, FileFilter, boolean, boolean, boolean)} instead.
1173     */
1174    @Deprecated
1175    public static ArrayList<File> getFileList(String path, boolean recursive, boolean wantDirectory, boolean wantHidden)
1176    {
1177        return getFileList(new File(getGenericPath(path)), null, recursive, wantDirectory, wantHidden);
1178    }
1179
1180    /**
1181     * @deprecated Use {@link #getFiles(File, FileFilter, boolean, boolean, boolean)} instead.
1182     */
1183    @Deprecated
1184    public static ArrayList<File> getFileList(File file, boolean recursive, boolean wantDirectory, boolean wantHidden)
1185    {
1186        return getFileList(file, null, recursive, wantDirectory, wantHidden);
1187    }
1188
1189    /**
1190     * @deprecated Use {@link #getFiles(File, FileFilter, boolean, boolean, boolean)} instead.
1191     */
1192    @Deprecated
1193    public static ArrayList<File> getFileList(File file, boolean recursive, boolean wantHidden)
1194    {
1195        return getFileList(file, recursive, false, wantHidden);
1196    }
1197
1198    /**
1199     * @deprecated Use {@link #getFiles(File, FileFilter, boolean, boolean, boolean)} instead.
1200     */
1201    @Deprecated
1202    public static ArrayList<File> getFileList(String path, boolean recursive, boolean wantHidden)
1203    {
1204        return getFileList(path, recursive, false, wantHidden);
1205    }
1206
1207    /**
1208     * @deprecated Use {@link #getFiles(String, FileFilter, boolean, boolean, boolean)} instead.
1209     */
1210    @Deprecated
1211    public static ArrayList<String> getFileListAsString(String path, FileFilter filter, boolean recursive,
1212            boolean wantDirectory, boolean wantHidden)
1213    {
1214        final ArrayList<File> files = getFileList(path, filter, recursive, wantDirectory, wantHidden);
1215        final ArrayList<String> result = new ArrayList<String>();
1216
1217        for (File file : files)
1218            result.add(file.getPath());
1219
1220        return result;
1221    }
1222
1223    /**
1224     * @deprecated Use {@link #getFiles(String, FileFilter, boolean, boolean, boolean)} instead.
1225     */
1226    @Deprecated
1227    public static ArrayList<String> getFileListAsString(String path, boolean recursive, boolean wantDirectory,
1228            boolean wantHidden)
1229    {
1230        return getFileListAsString(path, null, recursive, wantDirectory, wantHidden);
1231    }
1232
1233    /**
1234     * @deprecated Use {@link #getFiles(String, FileFilter, boolean, boolean, boolean)} instead.
1235     */
1236    @Deprecated
1237    public static ArrayList<String> getFileListAsString(String path, FileFilter filter, boolean recursive,
1238            boolean wantHidden)
1239    {
1240        return getFileListAsString(path, filter, recursive, false, wantHidden);
1241    }
1242
1243    /**
1244     * @deprecated Use {@link #getFiles(String, FileFilter, boolean, boolean, boolean)} instead.
1245     */
1246    @Deprecated
1247    public static ArrayList<String> getFileListAsString(String path, boolean recursive, boolean wantHidden)
1248    {
1249        return getFileListAsString(path, recursive, false, wantHidden);
1250    }
1251
1252    /**
1253     * Return true if the file described by specified path exists
1254     * 
1255     * @deprecated use {@link #exists(String)} instead
1256     */
1257    @Deprecated
1258    public static boolean exist(String path)
1259    {
1260        return exists(path);
1261    }
1262
1263    /**
1264     * Return true if the file described by specified path exists
1265     */
1266    public static boolean exists(String path)
1267    {
1268        return new File(getGenericPath(path)).exists();
1269    }
1270
1271    /**
1272     * Return true if the specified file is a directory
1273     */
1274    public static boolean isDirectory(String path)
1275    {
1276        return new File(getGenericPath(path)).isDirectory();
1277    }
1278
1279    /**
1280     * Return true if the specified file is a link<br>
1281     * Be careful, it does work in almost case, but not all
1282     * 
1283     * @throws IOException
1284     */
1285    public static boolean isLink(String path) throws IOException
1286    {
1287        return isLink(new File(getGenericPath(path)));
1288    }
1289
1290    /**
1291     * Return true if the specified file is a link<br>
1292     * Be careful, it does work in almost case, but not all
1293     * 
1294     * @throws IOException
1295     */
1296    public static boolean isLink(File file) throws IOException
1297    {
1298        if (file == null)
1299            return false;
1300
1301        final File canon;
1302
1303        if (file.getParent() == null)
1304            canon = file;
1305        else
1306            canon = new File(file.getParentFile().getCanonicalFile(), file.getName());
1307
1308        // we want to ignore case whatever system does
1309        return !canon.getCanonicalFile().getAbsolutePath().equalsIgnoreCase(canon.getAbsolutePath());
1310    }
1311
1312    public static boolean delete(String path, boolean recursive)
1313    {
1314        return delete(new File(getGenericPath(path)), recursive);
1315    }
1316
1317    public static boolean delete(File f, boolean recursive)
1318    {
1319        boolean result = true;
1320
1321        if (f.isDirectory())
1322        {
1323            final File[] files = f.listFiles();
1324
1325            // can return null...
1326            if (files != null)
1327            {
1328                // delete files
1329                for (File file : files)
1330                {
1331                    if (file.isDirectory())
1332                    {
1333                        if (recursive)
1334                            result = result & delete(file, true);
1335                    }
1336                    else
1337                        result = result & file.delete();
1338                }
1339            }
1340
1341            // then delete empty directory
1342            result = result & f.delete();
1343        }
1344        else if (f.exists())
1345        {
1346            final long start = System.currentTimeMillis();
1347
1348            // we can need that first to delete file
1349            if (!f.setWritable(true, false))
1350                f.setWritable(true, true);
1351
1352            result = f.delete();
1353
1354            // retry for locked file (we try for 15s max)
1355            while ((!result) && (System.currentTimeMillis() - start) < (10 * 1000))
1356            {
1357                // can help for file deletion...
1358                System.gc();
1359                ThreadUtil.sleep(1000);
1360
1361                // may help
1362                if (!f.setWritable(true, false))
1363                    f.setWritable(true, true);
1364
1365                result = f.delete();
1366            }
1367        }
1368
1369        return result;
1370    }
1371}