001package arcommon;
002
003import java.io.BufferedInputStream;
004import java.io.BufferedOutputStream;
005import java.io.BufferedReader;
006import java.io.BufferedWriter;
007import java.io.File;
008import java.io.FileFilter;
009import java.io.FileInputStream;
010import java.io.FileNotFoundException;
011import java.io.FileOutputStream;
012import java.io.FileReader;
013import java.io.FileWriter;
014import java.io.FilenameFilter;
015import java.io.IOException;
016import java.io.InputStreamReader;
017import java.io.OutputStreamWriter;
018import java.io.RandomAccessFile;
019import java.io.UnsupportedEncodingException;
020import java.nio.channels.FileChannel;
021import java.nio.channels.FileLock;
022import java.nio.channels.OverlappingFileLockException;
023import java.nio.file.Files;
024import java.nio.file.Path;
025import java.nio.file.Paths;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.Collection;
029import java.util.Collections;
030import java.util.Comparator;
031import java.util.Enumeration;
032import java.util.Iterator;
033import java.util.List;
034import java.util.Properties;
035
036import arcommon.Util.OsType;
037import arcommon.progress.ProgressState;
038import arcommon.progress.ProgressThreadData;
039import arcommon.ui.other.GUIUtil;
040/**
041 * A collection of miscellaneous file IO methods.
042 * <br>
043 * Terminology in naming is as follows:<br>
044 * &nbsp;&nbsp;&nbsp;Directory: The directory with its content (Name, number of files, last modified...), but not the path to the directory. Typically not a {@link java.lang.String}, but the directory name (without its path) is a {@link java.lang.String}.<br>
045 * &nbsp;&nbsp;&nbsp;Path: The path to a directory. A {@link java.lang.String} containing a list of directory name separated by {@link java.io.File#separator}. Must typically end with a {@link java.io.File#separator}.<br>
046 * &nbsp;&nbsp;&nbsp;File name: A {@link java.lang.String} containing the name of a file. May contain the path to the directory, where the file is placed. Must not end with a {@link java.io.File#separator}.<br>
047 */
048public class FileUtil {
049    public static final int MAXIMUM_WINDOWS_FILE_NAME_LENGTH = 259;
050    
051    public static final String WINDOWS_TEMP_PATH = "C:\\temp\\";
052    public static final String LINUX_TEMP_PATH = "/tmp/";
053    public static final String MAC_TEMP_PATH = "/tmp/";
054    
055    public static boolean warnIfBomHeaderRemoved = true;
056    
057    public static String calculateDriveName(String fileName) {
058        if (!Util.isWindowsOS()) {
059            throw new RuntimeException("Unsupported operating system");
060        }
061        if (fileName == null) {
062            return null;
063        }
064        fileName = calculateAbsoluteFileName(fileName);
065        int index = fileName.indexOf(File.separator);
066        if (index == -1) {
067            return fileName;
068        }
069        String driveName = fileName.substring(0, index);
070        if (!StringUtil.doesStringMatchRegExp(driveName, "[A-Z]\\:")) {
071            throw new RuntimeException("!StringUtil.doesStringMatchRegExp(driveName, \"[A-Z]\\:\") " + driveName);
072        }
073        return driveName;
074    }
075
076    public static String calculatePath(String fileName) {
077        if (fileName.endsWith(File.separator)) {
078            return fileName;
079        }
080        File file = new File(fileName);
081        File parentFile = file.getParentFile();
082        if (parentFile == null) {
083            return "";
084        }
085        String result = parentFile.getPath();
086        if (!result.endsWith(File.separator)) {
087            result += File.separator;
088        }
089        return result;
090    }
091
092    public static String calculatePathNoIO(String fileName) {
093        if (fileName.endsWith(File.separator)) {
094            return fileName;
095        }
096        int lastIndex = fileName.lastIndexOf(File.separator, fileName.length() - 2);
097        if (lastIndex == - 1) {
098            return "";
099        }
100        return fileName.substring(0, lastIndex + 1);
101    }
102
103    public static String addDirectorySeparatorToPath(String path) {
104        if (path.length() > 0 && !path.endsWith(File.separator)) {
105            path += File.separator;
106        }
107        return path;
108    }
109
110    public static String calculateExtension(String fileName) {
111        return calculateExtension(fileName, true);
112    }
113
114    public static String calculateExtension(String fileName, boolean withDot) {
115        String onlyFileName = calculateFileNameNoIO(fileName);
116        int position = onlyFileName.lastIndexOf('.');
117        if (position == -1) {
118            return "";
119        }
120        if (!withDot) {
121            return onlyFileName.substring(position + 1);
122        }
123        return onlyFileName.substring(position);
124    }
125
126    public static String calculateFileName(String fileName) {
127        if (fileName.endsWith(File.separator)) {
128            return "";
129        }
130        File file = new File(fileName);
131        String result = file.getName();
132        return result;
133    }
134
135    public static String calculateFileNameNoIO(String fileName) {
136        if (fileName.endsWith(File.separator)) {
137            return "";
138        }
139        int index = fileName.lastIndexOf(File.separator);
140        if (index == -1) {
141            return fileName;
142        }
143        return fileName.substring(index + 1);
144    }
145
146    public static String calculateFileNameNoExtension(String fileName) {
147        if (fileName.endsWith(File.separator)) {
148            return "";
149        }
150        File file = new File(fileName);
151        String result = file.getName();
152        int index = result.lastIndexOf('.');
153        if (index != -1) {
154            result = result.substring(0, index);
155        }
156        return result;
157    }
158
159    public static String calculateLastDirectoryName(String fileName) {
160        File file = new File(fileName);
161        File directory;
162        if (file.exists() && !file.isDirectory()) {
163            directory = file.getParentFile();
164        } else {
165            directory = file;
166        }
167        if (directory == null) {
168            return null;
169        }
170        String result = directory.getName();
171        return result;
172    }
173
174    public static String calculateLastDirectoryNameNoIO(String fileName) {
175        String path = calculatePathNoIO(fileName);
176        int lastIndex = path.lastIndexOf(File.separator, path.length() - 2);
177        if (lastIndex == - 1) {
178            return "";
179        }
180
181        return path.substring(lastIndex + 1, path.length() - 1);
182    }
183
184    public static String calculateParentPathFromPath(String path) {
185        validatePath(path);
186        File file = new File(path);
187        file = file.getParentFile();
188        if (file == null) {
189            return "";
190        }
191        String result = file.toString();
192        if (!result.endsWith(File.separator)) {
193            result += File.separator;
194        }
195        return result;
196    }
197
198    public static List<String> calculateAllDirectoryNames(String fileName) {
199        File file = new File(fileName);
200        File directory;
201        if (!file.isDirectory() && file.exists()) {
202            directory = file.getParentFile();
203        } else {
204            directory = file;
205        }
206
207        ArrayList<String> result = new ArrayList<String>();
208        while (directory != null) {
209            String directoryName = directory.getName();
210            if (directoryName.equals("")) {
211                directoryName = directory.toString();
212            } else {
213                directoryName += File.separator;
214            }
215            if (result.size() == 0) {
216                result.add(directoryName);
217            } else {
218                result.add(0, directoryName);
219            }
220            directory = directory.getParentFile();
221        }
222
223        return result;
224    }
225
226    /** Returns the absolute path name of path relative to current directory.
227     * <br>
228     * See {@link #calculateAbsolutePath(String, String)} for details.<br>
229     */
230    public static String calculateAbsolutePath(String path) {
231        return calculateAbsolutePath(null, path);
232    }
233
234    /** Returns the absolute path name of path relative to basePath.
235     * <br>
236     * See {@link #calculateAbsoluteFileName(String, String)} for details.<br>
237     */
238    public static String calculateAbsolutePath(String basePath, String path) {
239        path = calculateAbsoluteFileName(basePath, path);
240        if (!path.endsWith(File.separator)) {
241            path += File.separator;
242        }
243        return path;
244    }
245
246    /** Returns the absolute file name of fileName relative to current directory.
247     * <br>
248     * See {@link #calculateAbsoluteFileName(String, String)} for details.<br>
249     */
250    public static String calculateAbsoluteFileName(String fileName) {
251        return calculateAbsoluteFileName(null, fileName);
252    }
253    
254    /** Returns the absolute file name of fileName relative to basePath.
255     * <br>
256     * The absolute file name is calculated using the {@link java.io.File#getCanonicalFile()} method.<br>
257     * &nbsp;&nbsp;See {@link #calculateAbsoluteFileNameLocal(String)} for details.<br>
258     * If basePath is null the current directory is use as base.<br>
259     * All File.separator + File.separator in fileName will be replaced with File.separator ("//" -&gt; "/" and "\" -&gt; "\").
260     */
261    public static String calculateAbsoluteFileName(String basePath, String fileName) {
262        File file = new File(fileName);
263        if (file.isAbsolute()) {
264            // The file name is absolute (Starts with a File.separator), but may contain ".." or File.separator + File.separator.
265            if (fileName.contains("..") || fileName.contains(File.separator + File.separator)) {
266                return calculateAbsoluteFileNameLocal(fileName);
267            }
268            return fileName;
269        }
270        
271        if (basePath == null || basePath.length() == 0) {
272            basePath = currentPath();
273        }
274        if (basePath.endsWith(File.separator) && fileName.startsWith(File.separator)) {
275            fileName = fileName.substring(1);
276        }
277        if (!basePath.endsWith(File.separator) && !fileName.startsWith(File.separator)) {
278            basePath += File.separator;
279        }
280        
281        return calculateAbsoluteFileNameLocal(basePath + fileName);
282    }
283    
284    private static String calculateAbsoluteFileNameLocal(String fileName) {
285        while (fileName.contains(File.separator + File.separator)) {
286            fileName = StringUtil.replace(fileName, File.separator + File.separator, File.separator);
287        }
288        File file = new File(fileName);
289        try {
290            file = file.getCanonicalFile();
291        } catch (IOException e) {
292            throw new RuntimeException(e);
293        }
294        return file.toString();
295    }
296
297    public static boolean isAbsolute(String fileName) {
298        File file = new File(fileName);
299        return file.isAbsolute();
300    }
301
302    public static String convertTo8Dot3(String fileName) {
303        String[] strings = fileName.split("\\\\"); // Should be File.separator, but it don't work for \\. Would probably work for /.
304        String result = "";
305        for (int i = 0; i < strings.length; i++) {
306            if (i > 0) {
307                result += File.separator;
308            }
309            result += convertTo8Dot3Local(strings[i]);
310        }
311        return result;
312    }
313
314    private static String convertTo8Dot3Local(String fileName) {
315        if (fileName.indexOf(' ') == -1) {
316            return fileName;
317        }
318        fileName = fileName.replaceAll(" ", "");
319        int index = fileName.indexOf('.');
320        String extension = "";
321        if (index != -1) {
322            extension = fileName.substring(index);
323            if (extension.length() > 3 + 1) {
324                extension = extension.substring(0, 3 + 1);
325            }
326            fileName = fileName.substring(0, index);
327        }
328        if (fileName.length() > 6) {
329            fileName = fileName.substring(0, 6);
330        }
331        fileName += "~1" + extension; // MEGA HACK
332        return fileName;
333    }
334
335    private static boolean isPathEndingValid(String path) {
336        return path.endsWith(File.separator);
337    }
338    
339    private static void validatePath(String path) {
340        if (!isPathValid(path)) {
341            throw new RuntimeException("Path \"" + path + "\" is not valid");
342        }
343    }
344
345    public static boolean isPathValid(String path) {
346        if (!isPathEndingValid(path)) {
347            return false;
348        }
349        
350        return isPathOrFileNameValid(path);
351    }
352
353    private static boolean isFileNameEndingValid(String fileName) {
354        return !fileName.endsWith(File.separator);
355    }
356    
357    private static void validateFileName(String fileName) {
358        if (!isFileNameValid(fileName)) {
359            throw new RuntimeException("File name \"" + fileName + "\" is not valid");
360        }
361    }
362
363    public static boolean isFileNameValid(String fileName) {
364        if (!isFileNameEndingValid(fileName)) {
365            return false;
366        }
367        
368        return isPathOrFileNameValid(fileName);
369    }
370    
371    private static boolean isPathOrFileNameValid(String fileName) {
372        if (Util.isWindowsOS()) {
373            return isWindowsPathOrFileNameValid(fileName);
374        }
375
376        if (Util.isLinuxOS()) {
377            return isLinuxPathOrFileNameValid(fileName);
378        }
379        
380        return true;
381    }
382    
383    public static boolean isWindowsFileNameValid(String fileName) {
384        if (!isFileNameEndingValid(fileName)) {
385            return false;
386        }
387        
388        return isWindowsPathOrFileNameValid(fileName);
389    }
390    
391    private static boolean isWindowsPathOrFileNameValid(String fileName) {
392        if (fileName.endsWith(" ")) { // File name must not end with a space
393            return false;
394        }
395        
396        if (fileName.contains(" \\")) { // Directory name must not end with a space
397            return false;
398        }
399        
400        if (fileName.contains("\\\\")) { // Directory name must not be empty
401            return false;
402        }
403
404        if (fileName.contains("\\.\\")) { // Current directory is only allowed in the start of the path
405            return false;
406        }
407        
408        boolean result = StringUtil.doesStringMatchRegExp(fileName, "([A-Z]:){0,1}[a-zA-Z0-9 \\\\\\+-.,;!_=~&'%$#@æåøÆÅØüç(){}\\[\\]]*");
409        if (!result) {
410            return false;
411        }
412        
413        if (isWindowsFileNameToLong(fileName)) {
414            return false;
415        }
416        
417        return true;
418    }
419    
420    public static boolean isLinuxFileNameValid(String fileName) {
421        if (!isFileNameEndingValid(fileName)) {
422            return false;
423        }
424        
425        return isLinuxPathOrFileNameValid(fileName);
426    }
427    
428    private static boolean isLinuxPathOrFileNameValid(String fileName) {
429        if (fileName.contains("//")) { // Directory name must not be empty
430            return false;
431        }
432        
433        if (fileName.contains("/./")) { // Current directory is only allowed in the start of the path
434            return false;
435        }
436        
437        return true;
438    }
439    
440    public static boolean isFileNameToLong(String fileName) {
441        return Util.isWindowsOS() && isWindowsFileNameToLong(fileName);
442    }
443    
444    public static boolean isWindowsFileNameToLong(String fileName) {
445        return fileName.length() > MAXIMUM_WINDOWS_FILE_NAME_LENGTH;
446    }
447
448    public static void checkFileNameLengthAndWarn(String fileName) {
449        if (isFileNameToLong(fileName)) {
450            GUIUtil.showWarning("The file name \n     \"" + fileName + "\"\n  is too long (" + fileName.length() + " > " + MAXIMUM_WINDOWS_FILE_NAME_LENGTH + ")");
451        }
452    }
453
454    public static String replaceAllSpecialFileNameChars(String fileName) {
455        fileName = replaceSpecialFileNameAndDirectoryChars(fileName);
456        fileName = fileName.replaceAll("[^a-zA-Z0-9 \\-._]", "_");
457        return fileName;
458    }
459
460    public static String replaceSpecialDirectoryChars(String fileName) {
461        fileName = fileName.replace("..", "_");
462        fileName = fileName.replace('\\', '_');
463        fileName = fileName.replace('/', '_');
464        return fileName;
465    }
466
467    public static String replaceSpecialFileNameAndDirectoryChars(String fileName) {
468        fileName = replaceSpecialDirectoryChars(fileName);
469        fileName = replaceSpecialFullFileNameChars(fileName);
470        return fileName;
471    }
472
473    public static String replaceSpecialFullFileNameChars(String fileName) {
474        fileName = fileName.replace('$', '_');
475        fileName = fileName.replace('?', '_');
476        fileName = fileName.replace('<', '_');
477        fileName = fileName.replace('>', '_');
478        fileName = fileName.replace(';', '_');
479        fileName = fileName.replace('\"', '_');
480        fileName = fileName.replace('*', '_');
481        fileName = fileName.replace('%', '_');
482        return fileName;
483    }
484
485    public static boolean isDirectory(String fileName) {
486        if (!exists(fileName)) {
487            throw new RuntimeException("!exists(fileName): " + fileName);
488        }
489        return new File(fileName).isDirectory();
490    }
491
492    public static boolean isDirectoryCaseSensitive(String path) {
493        if (Util.isWindowsOS()) {
494            return false;
495        }
496
497        if (!exists(path)) {
498            throw new RuntimeException("Directory \"" + path + "\" doesn't exist");
499        }
500        if (!isDirectory(path)) {
501            throw new RuntimeException("Path \"" + path + "\" is not a directory");
502        }
503
504        if (!path.endsWith(File.separator)) {
505            path = path + File.separator;
506        }
507
508        String fileName = createTempFile(path, DEFAULT_TEMP_FILE_PREFIX + "FileUtil - isDirectoryCaseSensitive - ", ".txt");
509
510        boolean exists = exists(fileName);
511        if (!exists) {
512            throw new RuntimeException("Internal error " + fileName);
513        }
514
515        String fileNamePart = calculateFileName(fileName);
516        String fileNameLowerCase = path + fileNamePart.toLowerCase();
517        boolean lowerCaseExists = exists(fileNameLowerCase);
518
519        delete(fileName);
520
521        return !lowerCaseExists;
522    }
523
524    public static boolean canFilesInDirectoryBeMarkedExecutable(String path) {
525        if (Util.isWindowsOS()) {
526            return false;
527        }
528
529        if (!exists(path)) {
530            throw new RuntimeException("Directory \"" + path + "\" doesn't exist");
531        }
532        if (!isDirectory(path)) {
533            throw new RuntimeException("Path \"" + path + "\" is not a directory");
534        }
535
536        if (!path.endsWith(File.separator)) {
537            path = path + File.separator;
538        }
539
540        String fileName = createTempFile(path, DEFAULT_TEMP_FILE_PREFIX + "FileUtil - canFilesInDirectoryBeMarkedExecutable - ", ".txt");
541        
542        setExecutable(fileName, true);
543
544        boolean canExecute = getExecutable(fileName);
545
546        delete(fileName);
547
548        return canExecute;
549    }
550    
551    /**
552     * This method will return the file system of path.
553     * <br>
554     * NOTE: This method will return strings like these on Linux: "ext4", "fuseblk" (NTFS), "iso9660" (CD image on USB) and "vfat" (FAT32).<br>
555     * Code copied from <a href="https://stackoverflow.com/questions/16660918/determine-filesystem-type-of-given-file-in-java">https://stackoverflow.com/questions/16660918/determine-filesystem-type-of-given-file-in-java</a><br>
556     * and modified
557     * @see #calculateFileSystemType(String) see calculateFileSystemType for a method that will return kind of the same information.
558     */
559/*    public static String getFileSystemString(String path) {
560        try {
561            File pathFile = new File(path);
562            Path pathPath = Path.of(pathFile.getPath()); // Since 11
563            return Files.getFileStore(pathPath).type();
564        } catch (IOException e) {
565            throw new RuntimeException(e);
566        }
567    }*/
568    
569    public static enum FILE_SYSTEM {EXT4, NTFS, FAT32, UNKNOWN};
570    
571    /**
572     * This method will calculate the file system of path (in a very hacky way) and return it.
573     * <br>
574     * NOTE: This method requires that the path is writable. If it is not writable, UNKNOWN is returned.<br>
575     * NOTE: This method is slow as it creates and deletes files in path.<br>
576     * NOTE: This method has NOT been tested on Mac at all.
577     * See the uncomment method getFileSystemString(String) above for a method that will return kind of the same information.
578     */
579    public static FILE_SYSTEM calculateFileSystemType(String path) {
580        boolean writeAccess = FileUtil.checkWriteAccess(path);
581        if (!writeAccess) {
582            System.out.println("No write access to \"" + path + "\" => File system can't be determinant");
583            return FILE_SYSTEM.UNKNOWN;
584        }
585        
586        String existingParent = FileUtil.findExistingParent(path);
587        if (Util.isWindowsOS()) {
588            String fileName = FileUtil.createTempFile(existingParent, FileUtil.DEFAULT_TEMP_FILE_PREFIX + "FileUtil - getFileSystem - ", ".bat");
589            String outputFileNameStart = "Data";
590            String outputFileNameEnd = ":1234.txt"; // Note: The ':' is the NTFS data stream separator char (not "drive letter" "path" separator).
591            String outputFileName = outputFileNameStart + outputFileNameEnd;
592            String fullOutputFileName = existingParent + outputFileName;
593            FileUtil.writeFile(fileName, "echo This is test data > " + outputFileName, false, "Cp1252");
594            int exitCode = ProcessUtil.executeCommandNoInputNoOutput(fileName, new File(existingParent));
595            FileUtil.delete(fileName);
596            boolean exists = FileUtil.exists(fullOutputFileName);
597            if (exists && exitCode == 0) {
598                FileUtil.delete(fullOutputFileName);
599                exists = FileUtil.exists(fullOutputFileName);
600                if (exists) {
601                    throw new RuntimeException("exists " + fullOutputFileName);
602                }
603                String partlyOutputFileName = existingParent + outputFileNameStart;
604                FileUtil.delete(partlyOutputFileName);
605                exists = FileUtil.exists(partlyOutputFileName);
606                if (exists) {
607                    throw new RuntimeException("exists " + fullOutputFileName);
608                }
609                return FILE_SYSTEM.NTFS;
610            }
611            
612            return FILE_SYSTEM.FAT32;
613        }
614        
615        try {
616            // Files containing a ':' can only be created on EXT4 on Linux.
617            String fileName = FileUtil.createTempFile(existingParent, FileUtil.DEFAULT_TEMP_FILE_PREFIX + "FileUtil - getFileSystem - : - ", ".txt");
618            FileUtil.delete(fileName);
619            return FILE_SYSTEM.EXT4;
620        } catch (Exception e) {
621            ; // Exception ignored
622        }
623        
624        boolean directoryCaseSensitive = FileUtil.isDirectoryCaseSensitive(existingParent);
625        if (directoryCaseSensitive) {
626            // NTFS is (wrongly) case sensitive on Linux.
627            return FILE_SYSTEM.NTFS;
628        }
629        
630        return FILE_SYSTEM.FAT32;
631    }
632    
633    public static boolean exists(String fileName) {
634        return new File(fileName).exists();
635    }
636
637    public static long lastModified(String fileName) {
638        if (!exists(fileName)) {
639            throw new RuntimeException("!exists(fileName): " + fileName);
640        }
641        return new File(fileName).lastModified();
642    }
643    
644    public static void setLastModified(String fileName, long lastModified) {
645        if (!exists(fileName)) {
646            throw new RuntimeException("!exists(fileName): " + fileName);
647        }
648        File file = new File(fileName);
649        file.setLastModified(lastModified);
650    }
651
652    public static long size(String fileName) {
653        if (!exists(fileName)) {
654            throw new RuntimeException("!exists(fileName): " + fileName);
655        }
656        if (isDirectory(fileName)) {
657            throw new RuntimeException("isDirectory(fileName): " + fileName);
658        }
659        return new File(fileName).length();
660    }
661
662    public static boolean checkForExistsAndWarm(String fileName) {
663        if (exists(fileName)) {
664            if (isDirectory(fileName)) {
665                GUIUtil.showErrorMessage("\"" + fileName + "\" is a directory");
666                return false;
667            }
668            
669            GUIUtil.YesNoDialogAnswer answer = GUIUtil.showYesNoCancelDialog("File \"" + fileName + "\" already exists. Should it be overwritten?");
670            switch (answer) {
671                case YES :
672                    ;
673                break;
674                case NO :
675                    return false;
676//              break;
677                case CANCEL :
678                    return false;
679//              break;
680                default :
681                    throw new RuntimeException("Unknown answer " + answer);
682//              break;
683            }
684        }
685        
686        return true;
687    }
688
689    public static void mkdirs(String path) {
690        mkdirs(new File(path));
691    }
692
693    public static void mkdirs(File path) {
694        mkdirs(path, true);
695    }
696
697    public static boolean mkdirs(String path, boolean throwOnFail) {
698        return mkdirs(new File(path), throwOnFail);
699    }
700
701    public static synchronized boolean mkdirs(File path, boolean throwOnFail) {
702        if (path == null) {
703            throw new NullPointerException("path == null");
704        }
705        if (path.toString().length() == 0) {
706            throw new RuntimeException("path.toString().length() == 0");
707        }
708        if (path.exists()) {
709            return true;
710        }
711        path.mkdirs(); // This is not thread safe. Might return false, when an another thread has created the path.
712        // Solved by using "synchronized" and the extra "path.exists()" check after "mkdirs()"
713        boolean ok = path.exists();
714        if (!ok && throwOnFail) {
715            throw new RuntimeException("mkdirs() failed \"" + path + "\"");
716        }
717        return ok;
718    }
719
720    public static void mkdirsAndFile(String fileName) {
721        File toFile = new File(fileName);
722        if (toFile.exists()) {
723            return ;
724        }
725        File parentFile = toFile.getParentFile();
726        mkdirs(parentFile);
727        try {
728            if (!toFile.createNewFile()) {
729                throw new RuntimeException("Creation of file failed \"" + fileName + "\"");
730            }
731        } catch (IOException e) {
732            throw new RuntimeException(e);
733        }
734    }
735
736    public static String readFile(String fileName) {
737        return readFile(fileName, null);
738    }
739
740    public static String readFile(String fileName, String encoding) {
741        return readFile(fileName, encoding, -1);
742    }
743    
744    public static final int READ_NO_MAXIMUM_NUMBER_OF_BYTES = -1;
745
746    public static String readFile(String fileName, String encoding, int maximumNumberOfBytes) {
747        BufferedReader bufferedReader = null;
748        try {
749            bufferedReader = createBufferedReader(fileName, encoding);
750            
751            ProgressState progressState = ProgressThreadData.createProgressState();
752            if (progressState != null) {
753                progressState.setName("Reading text in file " + fileName);
754                long size = size(fileName);
755                progressState.setMaximum(maximumNumberOfBytes == READ_NO_MAXIMUM_NUMBER_OF_BYTES ? (int)(size) : maximumNumberOfBytes);
756                progressState.setUseUnits(true);
757            }
758            
759            char[] buffer = new char[maximumNumberOfBytes == READ_NO_MAXIMUM_NUMBER_OF_BYTES ? 100000 : maximumNumberOfBytes];
760            StringBuilder stringBuilder = new StringBuilder();
761            while (bufferedReader.ready()) {
762                int count = bufferedReader.read(buffer);
763                if (progressState != null) {
764                    progressState.setCurrent(progressState.getCurrent() + count);
765                }
766                stringBuilder.append(new String(buffer, 0, count));
767                if (maximumNumberOfBytes != - 1 && stringBuilder.length() >= maximumNumberOfBytes) {
768                    break;
769                }
770            }
771            
772            ProgressThreadData.disposeProgressState(progressState);
773            bufferedReader.close();
774            
775            String result = stringBuilder.toString();
776            result = removeUtf8Bom(fileName, encoding, result, warnIfBomHeaderRemoved, true);
777            if (maximumNumberOfBytes != READ_NO_MAXIMUM_NUMBER_OF_BYTES && stringBuilder.length() > maximumNumberOfBytes) {
778                return result.substring(0, maximumNumberOfBytes);
779            }
780            return result;
781        } catch (Exception e) {
782            if (bufferedReader != null) {
783                try {
784                    bufferedReader.close();
785                } catch (IOException e1) {
786                    throw new RuntimeException(e1);
787                }
788            }
789            System.out.println(e.getMessage());
790            System.out.println("fileName: " + fileName);
791            throw new RuntimeException(e);
792        }
793    }
794
795    public static BufferedReader createBufferedReader(String fileName, String encoding) {
796        try {
797            BufferedReader in;
798            if (encoding == null) {
799                in = new BufferedReader(new FileReader(fileName));
800            } else {
801                in = new BufferedReader(new InputStreamReader(new FileInputStream(new File(fileName)), encoding));
802            }
803            return in;
804        } catch (IOException e) {
805            throw new RuntimeException(e);
806        }
807    }
808
809    public static String readFirstLineInFile(String fileName) {
810        return readFirstLineInFile(fileName, null);
811    }
812
813    public static String readFirstLineInFile(String fileName, String encoding) {
814        BufferedReader in = null;
815        try {
816            in = createBufferedReader(fileName, encoding);
817            String line = "";
818            if (in.ready()) {
819                line = in.readLine();
820                line = removeUtf8Bom(fileName, encoding, line, warnIfBomHeaderRemoved, true);
821            }
822            in.close();
823            return line;
824        } catch (Exception e) {
825            if (in != null) {
826                try {
827                    in.close();
828                } catch (IOException e1) {
829                    throw new RuntimeException(e1);
830                }
831            }
832            System.out.println(e.getMessage());
833            System.out.println("fileName: " + fileName);
834            throw new RuntimeException(e);
835        }
836    }
837
838    public static interface LineReadInterface {
839        public boolean lineRead(String line);
840    }
841
842    public static List<String> readLinesInFile(String fileName) {
843        return readLinesInFile(fileName, (String)null);
844    }
845
846    public static List<String> readLinesInFile(String fileName, String encoding) {
847        final ArrayList<String> result = new ArrayList<String>();
848        readLinesInFile(fileName, encoding, new LineReadInterface() {
849            public boolean lineRead(String line) {
850                result.add(line);
851                return true;
852            }
853        });
854        return result;
855    }
856
857    public static void readLinesInFile(String fileName, LineReadInterface lineReadInterface) {
858        readLinesInFile(fileName, null, lineReadInterface);
859    }
860
861    public static void readLinesInFile(String fileName, String encoding, LineReadInterface lineReadInterface) {
862        BufferedReader in = null;
863        try {
864            in = createBufferedReader(fileName, encoding);
865            readLinesInFile(fileName, encoding, in, lineReadInterface);
866        } catch (Exception e) {
867            if (in != null) {
868                try {
869                    in.close();
870                } catch (IOException e1) {
871                    throw new RuntimeException(e1);
872                }
873            }
874            System.out.println("FileName: " + fileName);
875            throw new RuntimeException(e);
876        }
877    }
878
879    public static void readLinesInFile(String fileName, String encoding, BufferedReader in, LineReadInterface lineReadInterface) throws IOException {
880        ProgressState progressState = ProgressThreadData.createProgressState();
881        if (progressState != null) {
882            progressState.setName("Reading lines in file " + fileName);
883            long size = size(fileName);
884            progressState.setMaximum((int)(size));
885            progressState.setUseUnits(true);
886        }
887
888        boolean first = true;
889        while (in.ready()) {
890            String line = in.readLine();
891            if (first) {
892                first = false;
893                line = removeUtf8Bom(fileName, encoding, line, warnIfBomHeaderRemoved, true);
894            }
895            if (progressState != null) {
896                progressState.setCurrent(progressState.getCurrent() + line.length());
897            }
898            if (!lineReadInterface.lineRead(line)) {
899                break;
900            }
901        }
902
903        ProgressThreadData.disposeProgressState(progressState);
904        in.close();
905    }
906    
907    public static String removeUtf8Bom(String fileName, String encoding, String content, boolean warnIfRemoved, boolean exceptionOnMismatch) {
908        String tempContent = content;
909        if (tempContent.length() > 10) {
910            tempContent = tempContent.substring(0, 5);
911        }
912        
913        if (encoding == null) {
914            encoding = System.getProperty("file.encoding");
915        }
916
917        byte[] bytes;
918        try {
919            bytes = tempContent.getBytes("UTF-8");
920        } catch (UnsupportedEncodingException e) {
921            throw new RuntimeException(e);
922        }
923
924        if (!encoding.equals("UTF-8")) {
925            bytes = readDataFromFile(fileName, 10);
926        }
927        if (bytes.length > 2 && bytes[0] == -17 && bytes[1] == -69 && bytes[2] == -65) {
928            if (encoding.equals("UTF-8")) {
929                content = content.substring(1);
930            } else if (encoding.equals("Cp1252") || encoding.equals("ISO-8859-1")) {
931                if (exceptionOnMismatch) {
932                    throw new RuntimeException("The \"" + fileName + "\" file starts with a Windows UTF-8 BOM header, but the file has been requested to be read in " + encoding + " encoding");
933                }
934                content = content.substring(3);
935            } else {
936                throw new RuntimeException("Unknown encoding " + encoding);
937            }
938
939            if (warnIfRemoved) {
940                System.out.println("WARNING: The Windows UTF-8 BOM header was removed from the \"" + fileName + "\" file!");
941                Util.sleep(3000);
942            }
943        }
944
945        return content;
946    }
947
948    /**
949     * Read all data from fileName and return it.
950     */
951    public static byte[] readDataFromFile(String fileName) {
952        return readDataFromFile(fileName, READ_NO_MAXIMUM_NUMBER_OF_BYTES);
953    }
954    
955    /**
956     * Read data from fileName and return it.
957     * <br>
958     * If maximumNumberOfBytes is different from {@link #READ_NO_MAXIMUM_NUMBER_OF_BYTES} it is used as max read limit.
959     * See {@link #readDataFromFile(String, long, int)} for very similar code
960     */
961    public static byte[] readDataFromFile(String fileName, int maximumNumberOfBytes) {
962        BufferedInputStream in = null;
963        try {
964            in = new BufferedInputStream(new FileInputStream(fileName));
965            int size = (int)size(fileName);
966            if (maximumNumberOfBytes != READ_NO_MAXIMUM_NUMBER_OF_BYTES && size > maximumNumberOfBytes) {
967                size = maximumNumberOfBytes;
968            }
969            byte[] result = new byte[size];
970            in.read(result);
971            in.close();
972            return result;
973        } catch (Exception e) {
974            if (in != null) {
975                try {
976                    in.close();
977                } catch (IOException e1) {
978                    throw new RuntimeException(e1);
979                }
980            }
981            System.out.println(e.getMessage());
982            System.out.println("FileName: " + fileName);
983            throw new RuntimeException(e);
984        }
985    }
986
987    /**
988     * Read data from offset in fileName and return it.
989     * <br>
990     * If maximumNumberOfBytes is different from {@link #READ_NO_MAXIMUM_NUMBER_OF_BYTES} it is used as max read limit.
991     * See {@link #readDataFromFile(String, int)} for very similar code
992     */
993    public static byte[] readDataFromFile(String fileName, long offset, int maximumNumberOfBytes) {
994        RandomAccessFile in = null;
995        try {
996            in = new RandomAccessFile(fileName, "r");
997            in.seek(offset);
998            long size = size(fileName);
999            if (maximumNumberOfBytes != READ_NO_MAXIMUM_NUMBER_OF_BYTES && size - offset > maximumNumberOfBytes) {
1000                size = maximumNumberOfBytes;
1001            } else if (offset > 0) {
1002                size -= offset;
1003            }
1004            if (size > Integer.MAX_VALUE) {
1005                size = Integer.MAX_VALUE;
1006            }
1007            byte[] result = new byte[(int)size];
1008            in.read(result);
1009            in.close();
1010            return result;
1011        } catch (Exception e) {
1012            if (in != null) {
1013                try {
1014                    in.close();
1015                } catch (IOException e1) {
1016                    throw new RuntimeException(e1);
1017                }
1018            }
1019            System.out.println(e.getMessage());
1020            System.out.println("FileName: " + fileName);
1021            throw new RuntimeException(e);
1022        }
1023    }
1024    
1025    public static void writeFile(String fileName, String content) {
1026        writeFile(fileName, content, false);
1027    }
1028
1029    public static void writeFile(String fileName, String content, boolean append) {
1030        writeFile(fileName, content, append, null);
1031    }
1032
1033    public static void writeFile(String fileName, String content, boolean append, String encoding) {
1034        writeFile(fileName, content, append, encoding, false);
1035    }
1036    
1037    private static void writeFile(String fileName, String content, boolean append, String encoding, boolean endWithNewline) {
1038        ArrayList<String> lines = new ArrayList<String>();
1039        if (content.length() > 0) {
1040            String[] split = content.split("\n", StringUtil.STRING_SPLIT_INCLUDING_TRAILING_EMPTY_STRINGS);
1041            for (int i = 0; i < split.length; i++) {
1042                split[i] = split[i].replaceAll("\r", "");
1043                lines.add(split[i]);
1044            }
1045        }
1046        writeLinesToFile(fileName, lines, append, encoding, null, endWithNewline);
1047    }
1048
1049    public static void writeAppendLinesToFile(String fileName, String lines) {
1050        writeAppendLinesToFile(fileName, lines, null);
1051    }
1052
1053    public static void writeAppendLinesToFile(String fileName, String lines, String encoding) {
1054        writeFile(fileName, lines, true, encoding, true);
1055    }
1056
1057    public static enum Newline {OS_DEFAULT, WINDOWS_NEWLINE, LINUX_NEWLINE, MAC_NEWLINE};
1058
1059    public static void writeLinesToFile(String fileName, Collection<String> lines) {
1060        writeLinesToFile(fileName, lines, false);
1061    }
1062
1063    public static void writeLinesToFile(String fileName, Collection<String> lines, boolean append) {
1064        writeLinesToFile(fileName, lines, append, null);
1065    }
1066
1067    public static void writeLinesToFile(String fileName, Collection<String> lines, boolean append, String encoding) {
1068        writeLinesToFile(fileName, lines, append, encoding, null, true);
1069    }
1070
1071    public static BufferedWriter createBufferedWriter(String fileName, boolean append, String encoding) {
1072        validateFileName(fileName);
1073
1074        BufferedWriter out;
1075        try {
1076            if (encoding == null) {
1077                out = new BufferedWriter(new FileWriter(fileName, append));
1078            } else {
1079                out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName, append), encoding));
1080            }
1081        } catch (IOException e) {
1082            throw new RuntimeException(e);
1083        }
1084        return out;
1085    }
1086
1087    public static void writeLinesToFile(String fileName, Collection<String> lines, boolean append, String encoding, Newline newline, boolean endWithNewline) {
1088        File toDirectory = new File(fileName).getParentFile();
1089        if (toDirectory != null && !toDirectory.exists()) {
1090            mkdirs(toDirectory);
1091        }
1092        if (!append && exists(fileName)) {
1093            delete(fileName);
1094        }
1095        try {
1096            BufferedWriter out = createBufferedWriter(fileName, append, encoding);
1097            if (newline == null) {
1098                newline = Newline.OS_DEFAULT;
1099            }
1100            Iterator<String> i = lines.iterator();
1101            while (i.hasNext()) {
1102                String line = i.next();
1103                out.write(line);
1104                if (endWithNewline || i.hasNext()) {
1105                    switch (newline) {
1106                        case OS_DEFAULT :
1107                            out.newLine();
1108                        break;
1109                        case WINDOWS_NEWLINE :
1110                            out.write("\r\n");
1111                        break;
1112                        case LINUX_NEWLINE :
1113                            out.write("\n");
1114                        break;
1115                        case MAC_NEWLINE :
1116                            out.write("\n");
1117                        break;
1118                        default :
1119                            throw new RuntimeException("Internal error");
1120    //                  break;
1121                    }
1122                }
1123            }
1124            out.close();
1125        } catch (Exception e) {
1126            throw new RuntimeException(e);
1127        }
1128    }
1129
1130    public static void writeDataToFile(String fileName, byte[] data, boolean append) {
1131        validateFileName(fileName);
1132        try {
1133            File toDirectory = new File(fileName).getParentFile();
1134            if (toDirectory != null && !toDirectory.exists()) {
1135                mkdirs(toDirectory);
1136            }
1137            BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(fileName, append));
1138            out.write(data);
1139            out.close();
1140        } catch (Exception e) {
1141            System.out.println("FileName: " + fileName);
1142            throw new RuntimeException(e);
1143        }
1144    }
1145
1146    public static final String DEFAULT_TEMP_FILE_PREFIX = "Temp file - ";
1147
1148    public static abstract class WriteUsingTempFileAction {
1149        public String createTempFile(String toFileName) {
1150            return FileUtil.createTempFile(calculatePath(toFileName), DEFAULT_TEMP_FILE_PREFIX + calculateFileNameNoExtension(toFileName) + " - ", calculateExtension(toFileName));
1151        }
1152
1153        public void saveFile(String tempFileName, boolean append, String encoding) throws IOException {
1154            BufferedWriter out = createBufferedWriter(tempFileName, append, encoding);
1155            writeContentOfFile(out);
1156            out.close();
1157        }
1158
1159        public abstract void writeContentOfFile(BufferedWriter out) throws IOException;
1160
1161        public boolean backupOldFile() {
1162            return true;
1163        }
1164
1165        public int getStartBackupNumber() {
1166            return 0;
1167        }
1168
1169        public String calculateBackupPath(int number, String toFileName) {
1170            return calculatePath(toFileName);
1171        }
1172
1173        public String calculateBackupFileName(int number, String toFileName) {
1174            return calculateNumberedFileName(calculateBackupPath(number, toFileName), toFileName, number);
1175        }
1176
1177        public void saveBackupNumberAndFileName(int number, String backupFileName) {
1178            ;
1179        }
1180    }
1181
1182    public static String calculateNumberedFileName(String path, String fileName, int number) {
1183        return path + calculateFileNameNoExtension(fileName) + " ~ " + number + " ~ " + calculateExtension(fileName);
1184    }
1185
1186    public static void writeUsingTempFile(String fileName, WriteUsingTempFileAction saveUsingTempFileAction) {
1187        writeUsingTempFile(fileName, null, saveUsingTempFileAction);
1188    }
1189
1190    public static void writeUsingTempFile(String fileName, String encoding, WriteUsingTempFileAction saveUsingTempFileAction) {
1191        writeUsingTempFile(fileName, false, encoding, saveUsingTempFileAction);
1192    }
1193
1194    public static void writeUsingTempFile(String fileName, boolean append, String encoding, WriteUsingTempFileAction saveUsingTempFileAction) {
1195        String tempFileName;
1196        String toFileName = fileName;
1197        boolean isTempFileUsed;
1198        if (exists(toFileName) && !append) {
1199            tempFileName = saveUsingTempFileAction.createTempFile(toFileName);
1200            isTempFileUsed = true;
1201        } else {
1202            tempFileName = toFileName;
1203            isTempFileUsed = false;
1204        }
1205
1206        try {
1207            saveUsingTempFileAction.saveFile(tempFileName, append, encoding);
1208        } catch (IOException e) {
1209            e.printStackTrace();
1210            try {
1211                long freeSpace = getFreeSpace(tempFileName);
1212                if (freeSpace == 0) {
1213                    GUIUtil.showInformation("The file was not saved, because there is no more free space on the disk!!!");
1214                }
1215            } catch (Exception e1) {
1216                e1.printStackTrace();
1217            }
1218            throw new RuntimeException(e);
1219        }
1220
1221        if (isTempFileUsed) {
1222            if (!saveUsingTempFileAction.backupOldFile()) {
1223                delete(toFileName);
1224            } else {
1225                int number = saveUsingTempFileAction.getStartBackupNumber();
1226                do {
1227                    String backupFileName = saveUsingTempFileAction.calculateBackupFileName(++number, fileName);
1228                    if (exists(backupFileName)) {
1229                        continue;
1230                    }
1231
1232                    String backupPath = calculatePath(backupFileName);
1233                    mkdirs(backupPath);
1234                    if (!rename(toFileName, backupFileName, false)) {
1235                        try {
1236                            long freeSpace = getFreeSpace(backupFileName);
1237                            if (freeSpace == 0) {
1238                                GUIUtil.showInformation("The file was not saved, because there is no more free space on the disk!!!");
1239                            }
1240                        } catch (Exception e1) {
1241                            e1.printStackTrace();
1242                        }
1243                        throw new RuntimeException("The file \"" + toFileName + "\" could not be renamed to \"" + backupFileName + "\"");
1244                    }
1245
1246                    saveUsingTempFileAction.saveBackupNumberAndFileName(number, backupFileName);
1247                    break;
1248                } while (true);
1249            }
1250
1251            rename(tempFileName, toFileName, true);
1252        }
1253    }
1254    
1255    public static String findFileNameWithCorrectCase(String fileName) {
1256        try {
1257            String path = new File(fileName).getCanonicalPath();
1258            if (exists(fileName) && isDirectory(fileName)) {
1259                path += File.separator;
1260            }
1261            return path;
1262        } catch (IOException e) {
1263            throw new RuntimeException(e);
1264        }
1265    }
1266    
1267    public static String findFileNameWithCorrectCaseLinux(String fileName, boolean throwOnFail) {
1268        String path = calculatePath(fileName);
1269        List<String> allDirectoryNames = calculateAllDirectoryNames(path);
1270        String fileNameOnly = calculateFileName(fileName);
1271        if (fileNameOnly.length() > 0) {
1272            allDirectoryNames.add(fileNameOnly);
1273        }
1274        
1275        String result = "";
1276        for (String searchDirectoryName : allDirectoryNames) {
1277            if (searchDirectoryName.equals("/")) {
1278                result += searchDirectoryName;
1279                continue;
1280            }
1281            String end = "";
1282            if (searchDirectoryName.endsWith("/")) {
1283                end = "/";
1284            }
1285            
1286            String[] list = new File(result).list();
1287            boolean found = false;
1288            String foundDirectoryName = null;
1289            if (list == null) {
1290                if (throwOnFail) {
1291                    throw new RuntimeException("!found " + searchDirectoryName + " " + fileName);
1292                }
1293                return null;
1294            }
1295            for (String existingDirectoryName : list) {
1296                if (searchDirectoryName.equalsIgnoreCase(existingDirectoryName + end)) {
1297                    if (found) {
1298                        if (throwOnFail) {
1299                            throw new RuntimeException("found " + existingDirectoryName + " " + foundDirectoryName + " " + fileName);
1300                        }
1301                        return null;
1302                    }
1303                    foundDirectoryName = existingDirectoryName;
1304                    found = true;
1305                }
1306            }
1307            if (!found) {
1308                if (throwOnFail) {
1309                    throw new RuntimeException("!found " + searchDirectoryName + " " + fileName);
1310                }
1311                return null;
1312            }
1313            result += foundDirectoryName + end;
1314        }
1315        
1316        return result;
1317    }
1318    
1319    public static boolean renameWithCaseChangeFullPath(String fromFileName, String toFileName, boolean throwOnFail) {
1320        List<String> fromAllDirectoryNames = calculateAllDirectoryNames(fromFileName);
1321        List<String> toAllDirectoryNames = calculateAllDirectoryNames(toFileName);
1322        int min = Math.min(fromAllDirectoryNames.size(), toAllDirectoryNames.size());
1323        String fromPath = "";
1324        String toPath = "";
1325        for (int i = 0; i < min; i++) {
1326            String fromDirectoryName = fromAllDirectoryNames.get(i);
1327            String toDirectoryName = toAllDirectoryNames.get(i);
1328            fromPath += fromDirectoryName;
1329            toPath += toDirectoryName;
1330            if (fromDirectoryName.equals(toDirectoryName)) {
1331                continue;
1332            }
1333            if (!fromDirectoryName.equalsIgnoreCase(toDirectoryName)) {
1334                break;
1335            }
1336            if (!renameWithCaseChange(fromPath, toPath, throwOnFail)) {
1337                return false;
1338            }
1339        }
1340        
1341        if (exists(toFileName) && isDirectory(toFileName)) {
1342            return true;
1343        }
1344        
1345        return renameWithCaseChange(fromFileName, toFileName, throwOnFail);
1346    }
1347
1348    public static boolean renameWithCaseChange(String fromFileName, String toFileName, boolean throwOnFail) {
1349        if (fromFileName.equals(toFileName)) {
1350            return true;
1351        }
1352
1353        File fromFile = new File(fromFileName);
1354        String fromFileNameLowerCase = fromFileName.toLowerCase();
1355        String toFileNameLowerCase = toFileName.trim().toLowerCase();
1356        if (fromFileNameLowerCase.equals(toFileNameLowerCase)) {
1357            String path = calculatePath(fromFileName);
1358            if (fromFile.isDirectory()) {
1359                path = path.substring(0, path.length() - 1);
1360            }
1361            int number = 1;
1362            do {
1363                String tempFileName = path + number++ + ".temp";
1364                File tempFile = new File(tempFileName);
1365                if (!tempFile.exists()) {
1366                    if (!fromFile.renameTo(tempFile)) {
1367                        if (throwOnFail) {
1368                            throw new RuntimeException("!fromFile.renameTo(tempFile) \"" + fromFile + "\" \"" + tempFile + "\"");
1369                        }
1370                        return false;
1371                    }
1372                    fromFile = tempFile;
1373                    break;
1374                }
1375            } while (true);
1376        }
1377        
1378        String path = calculatePath(toFileName);
1379        if (fromFile.isDirectory()) {
1380            path = calculateParentPathFromPath(path);
1381        }
1382        mkdirs(path);
1383        
1384        File toFile = new File(toFileName);
1385        if (!fromFile.renameTo(toFile)) {
1386            if (throwOnFail) {
1387                throw new RuntimeException("!fromFile.renameTo(toFile) " + fromFile + " " + toFile);
1388            }
1389            return false;
1390        }
1391        return true;
1392    }
1393
1394    public static void rename(String fromFileName, String toFileName) {
1395        rename(fromFileName, toFileName, true);
1396    }
1397
1398    public static boolean rename(String fromFileName, String toFileName, boolean throwOnFail) {
1399        return rename(new File(fromFileName), new File(toFileName), throwOnFail);
1400    }
1401
1402    public static boolean rename(File fromFileName, File toFileName, boolean throwOnFail) {
1403        validateFileName(toFileName.toString());
1404        if (toFileName.exists()) {
1405            if (throwOnFail) {
1406                throw new RuntimeException("toFileName.exists() \"" + toFileName + "\"");
1407            }
1408            return false;
1409        }
1410        boolean renamed = fromFileName.renameTo(toFileName);
1411        if (renamed) {
1412            if (fromFileName.exists()) {
1413                if (throwOnFail) {
1414                    throw new RuntimeException("fromFileName.exists after rename\"" + fromFileName + "\"");
1415                }
1416                return false;
1417            }
1418        } else {
1419            if (Util.isWindowsOS()) {
1420                renamed = renameWithCaseChange(fromFileName.toString(), toFileName.toString(), throwOnFail);
1421                if (renamed) {
1422                    return renamed;
1423                }
1424            }
1425            
1426            if (fromFileName.isDirectory()) {
1427                throw new RuntimeException("Rename / move of directories across mount point / drives is not supported.\n"
1428                        + "From file name: \"" + fromFileName.toString() + "\"\n"
1429                        + "To file name:   \"" + toFileName.toString() + "\"\n"
1430                        + "(Or some other rename error occurred)");
1431            }
1432
1433            boolean ok = copy(fromFileName.toString(), toFileName.toString(), false, true, throwOnFail);
1434            if (!ok) {
1435                return false;
1436            }
1437            if (!delete(fromFileName.toString(), throwOnFail)) {
1438                if (throwOnFail) {
1439                    throw new RuntimeException("The file \"" + fromFileName + "\" could not be deleted");
1440                }
1441                return false;
1442            }
1443        }
1444        return true;
1445    }
1446
1447    public static void copy(String fromFileName, String toFileName) {
1448        copy(fromFileName, toFileName, false);
1449    }
1450
1451    public static void copy(String fromFileName, String toFileName, boolean overwriteOnExists) {
1452        copy(fromFileName, toFileName, overwriteOnExists, true, true);
1453    }
1454
1455    public static boolean copy(String fromFileName, String toFileName, boolean overwriteOnExists, boolean copyLastModified, boolean throwOnFail) {
1456        if (fromFileName.equals(toFileName)) {
1457            if (throwOnFail) {
1458                throw new RuntimeException("ERROR: File names are equals \"" + fromFileName + "\" \"" + toFileName + "\"");
1459            }
1460            return false;
1461        }
1462
1463        File fromFile = new File(fromFileName);
1464        String fromFileNameLowerCase = fromFileName.toLowerCase();
1465        String toFileNameLowerCase = toFileName.trim().toLowerCase();
1466        if (fromFileNameLowerCase.equals(toFileNameLowerCase) && Util.isWindowsOS()) {
1467            if (throwOnFail) {
1468                throw new RuntimeException("ERROR: File names in lower case are equal \"" + fromFileName + "\" \"" + toFileName + "\"");
1469            }
1470            return false;
1471        }
1472
1473        if (!fromFile.exists()) {
1474            throw new RuntimeException("Internal error: From file \"" + fromFile + "\" doesn't exist!");
1475        }
1476        if (fromFile.isDirectory()) {
1477            throw new RuntimeException("From file \"" + fromFile + "\" is a directory, and copying directories is not supported");
1478        }
1479
1480        BufferedInputStream in;
1481        try {
1482            in = new BufferedInputStream(new FileInputStream(fromFileName));
1483        } catch (Exception e) {
1484            throw new RuntimeException(e);
1485        }
1486
1487        long inLength = fromFile.length();
1488        boolean result = copy(in, inLength, toFileName, overwriteOnExists, throwOnFail);
1489        if (!result) {
1490            return result;
1491        }
1492
1493        if (copyLastModified) {
1494            long lastModified = fromFile.lastModified();
1495            setLastModified(toFileName, lastModified);
1496        }
1497
1498        if (Util.isLinuxOS()) {
1499            boolean canRead = fromFile.canRead();
1500            boolean canWrite = fromFile.canWrite();
1501            boolean canExecute = fromFile.canExecute();
1502            if (canRead) {
1503                if (!setReadable(toFileName, true, throwOnFail)) {
1504                    return false;
1505                }
1506            }
1507            if (canWrite) {
1508                if (!setWritable(toFileName, true, throwOnFail)) {
1509                    return false;
1510                }
1511            }
1512            if (canExecute) {
1513                if (!setExecutable(toFileName, true, throwOnFail)) {
1514                    return false;
1515                }
1516            }
1517        }
1518
1519        // TODO Remove this extra verification
1520        if (!compareFiles(fromFileName, toFileName)) {
1521            throw new RuntimeException("Internal error: The files are not equals!!!");
1522        }
1523
1524        return result;
1525    }
1526    
1527    public static final long COPY_IN_LENGTH_UNKNOWN = -1;
1528
1529    public static boolean copy(BufferedInputStream in, long inLength, String toFileName, boolean overwriteOnExists, boolean throwOnFail) {
1530        validateFileName(toFileName);
1531        File toDirectory = new File(calculatePath(toFileName));
1532        if (toDirectory.toString().length() != 0) {
1533            mkdirs(toDirectory, throwOnFail);
1534        }
1535        if (exists(toFileName)) {
1536            if (!overwriteOnExists) {
1537                if (throwOnFail) {
1538                    throw new RuntimeException("ERROR: To file already exists \"" + toFileName + "\"");
1539                }
1540                return false;
1541            }
1542        }
1543
1544        try {
1545            ProgressState progressState = ProgressThreadData.createProgressState();
1546            if (progressState != null) {
1547                progressState.setName("Copying file " + toFileName);
1548                if (inLength != COPY_IN_LENGTH_UNKNOWN) {
1549                    progressState.setMaximum((int)(inLength));
1550                }
1551                progressState.setUseUnits(true);
1552            }
1553
1554            byte[] buffer = new byte[1000000];
1555            BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(toFileName));
1556            while (true) {
1557                int count = in.read(buffer);
1558                if (count == -1) {
1559                    break;
1560                }
1561                out.write(buffer, 0, count);
1562                if (progressState != null) {
1563                    progressState.setCurrent(progressState.getCurrent() + count);
1564                }
1565            }
1566            out.close();
1567            in.close();
1568
1569            ProgressThreadData.disposeProgressState(progressState);
1570
1571            if (!exists(toFileName)) {
1572                throw new RuntimeException("Internal error: To file doesn't exist!");
1573            }
1574            File toFile = new File(toFileName);
1575            if (inLength != COPY_IN_LENGTH_UNKNOWN && inLength != toFile.length()) {
1576                throw new RuntimeException("Internal error: From and to file length are different: " + inLength + " " + toFile.length());
1577            }
1578        } catch (Exception e) {
1579            if (in != null) {
1580                try {
1581                    in.close();
1582                } catch (IOException e1) {
1583                    throw new RuntimeException(e1);
1584                }
1585            }
1586            if (throwOnFail) {
1587                throw new RuntimeException(e);
1588            }
1589
1590            System.out.println(e.getMessage());
1591            System.out.println("To file name: " + toFileName);
1592            e.printStackTrace();
1593
1594            return false;
1595        }
1596
1597        return true;
1598    }
1599
1600    public static void copyDirectoryTree(String fromPath, String toPath, boolean echo) {
1601        copyDirectoryTree(fromPath, toPath, echo, false);
1602    }
1603
1604    public static void copyDirectoryTree(String fromPath, String toPath, boolean echo, boolean overwriteOnExists) {
1605        copyDirectoryTree(fromPath, toPath, echo, overwriteOnExists, true, true);
1606    }
1607
1608    public static boolean copyDirectoryTree(String fromPath, String toPath, boolean echo, boolean overwriteOnExists, boolean copyLastModified, boolean throwOnFail) {
1609        Collection<String> files = findAllFilesInDirectory(fromPath, true);
1610        for (Iterator<String> iterator = files.iterator(); iterator.hasNext();) {
1611            String fileName = iterator.next();
1612            if (!fileName.startsWith(fromPath)) {
1613                throw new RuntimeException("Internal error");
1614            }
1615            String offsetFileName = fileName.substring(fromPath.length());
1616            String newFileName = toPath + offsetFileName;
1617            boolean result = copy(fileName, newFileName, overwriteOnExists, copyLastModified, throwOnFail);
1618            if (!result) {
1619                return false;
1620            }
1621            if (echo) {
1622                System.out.println("File copied from \"" + fileName + "\" to \"" + newFileName + "\"");
1623            }
1624        }
1625        return true;
1626    }
1627    
1628    public static void delete(String fileName) {
1629        delete(fileName, true);
1630    }
1631
1632    public static boolean delete(String fileName, boolean throwOnFail) {
1633        if (!exists(fileName)) {
1634            if (throwOnFail) {
1635                throw new RuntimeException("File \"" + fileName + "\" doesn't exist");
1636            }
1637            return false;
1638        }
1639        if (!new File(fileName).delete()) {
1640            if (throwOnFail) {
1641                throw new RuntimeException("Delete of \"" + fileName + "\" failed");
1642            }
1643            return false;
1644        }
1645        return true;
1646    }
1647
1648    public static class DeleteDirectoryData {
1649        public List<String> filesDeleted;
1650        public List<String> directoriesDeleted;
1651        public boolean failed;
1652        
1653        @Override
1654        public String toString() {
1655            String result = "";
1656            if (filesDeleted != null && filesDeleted.size() > 0) {
1657                result = "Files: " + filesDeleted;
1658            }
1659            if (directoriesDeleted != null && directoriesDeleted.size() > 0) {
1660                if (result.length() > 0) {
1661                    result += "\n";
1662                }
1663                result += "Directories: " + directoriesDeleted + "\n";
1664            }
1665            if (failed) {
1666                if (result.length() > 0) {
1667                    result += "\n";
1668                }
1669                result += "Failed";
1670            }
1671            return result;
1672        }
1673    }
1674
1675    public static DeleteDirectoryData deleteDirectoryTree(String path, boolean echo) {
1676        return deleteDirectoryTree(path, echo, true);
1677    }
1678
1679    public static DeleteDirectoryData deleteDirectoryTree(String path, boolean echo, boolean throwOnFail) {
1680        DeleteDirectoryData deleteDirectoryData = new DeleteDirectoryData();
1681        Collection<String> files = findAllFilesInDirectory(path, true);
1682        for (String fileName : files) {
1683            boolean deleted = delete(fileName, throwOnFail);
1684            if (!deleted) {
1685                deleteDirectoryData.failed = true;
1686                
1687                if (echo) {
1688                    System.out.println("Deletion of the file \"" + fileName + "\" failed");
1689                }
1690                
1691                return deleteDirectoryData;
1692            }
1693            
1694            if (deleteDirectoryData.filesDeleted == null) {
1695                deleteDirectoryData.filesDeleted = new ArrayList<String>();
1696            }
1697            deleteDirectoryData.filesDeleted.add(fileName);
1698            
1699            if (echo) {
1700                System.out.println("The file \"" + fileName + "\" was deleted");
1701            }
1702        }
1703        
1704        List<String> allDirectoriesInDirectory = findAllDirectoriesInDirectoryAsList(path, true);
1705        // Hack to make sure that subdirectories are deleted before parent directory.
1706        Collections.sort(allDirectoriesInDirectory, new Comparator<String>() {
1707            public int compare(String s1, String s2) {
1708                return s2.length() - s1.length();
1709            }
1710        });
1711        
1712        for (String childPath : allDirectoriesInDirectory) {
1713            if (!deleteDirectory(deleteDirectoryData, childPath, echo, throwOnFail)) {
1714                return deleteDirectoryData;
1715            }
1716        }
1717        
1718        deleteDirectory(deleteDirectoryData, path, echo, throwOnFail);
1719        
1720        return deleteDirectoryData;
1721    }
1722    
1723    private static boolean deleteDirectory(DeleteDirectoryData deleteDirectoryData, String path, boolean echo, boolean throwOnFail) {
1724        boolean deleted = delete(path, throwOnFail);
1725        if (!deleted) {
1726            deleteDirectoryData.failed = true;
1727            
1728            if (echo) {
1729                System.out.println("Deletion of the directory \"" + path + "\" failed");
1730            }
1731            
1732            return false;
1733        }
1734        
1735        if (deleteDirectoryData.directoriesDeleted == null) {
1736            deleteDirectoryData.directoriesDeleted = new ArrayList<String>();
1737        }
1738        deleteDirectoryData.directoriesDeleted.add(path);
1739        
1740        if (echo) {
1741            System.out.println("The directory \"" + path + "\" was deleted");
1742        }
1743        
1744        return true;
1745    }
1746    
1747    public static class DirectoryTree {
1748        public String name;
1749        public String fullName;
1750        public List<DirectoryTree> subdirectories;
1751        public String toString() {
1752            return fullName;
1753        }
1754    }
1755
1756    public static List<String> findAllDirectoriesInDirectoryAsList(String path, boolean includeSubdirectories) {
1757        return findAllDirectoriesInDirectoryAsList(path, includeSubdirectories, null);
1758    }
1759
1760    public static List<String> findAllDirectoriesInDirectoryAsList(String path, boolean includeSubdirectories, List<String> excludeDirectories) {
1761        return findAllDirectoriesInDirectoryAsList(path, includeSubdirectories, excludeDirectories, null);
1762    }
1763
1764    public static List<String> findAllDirectoriesInDirectoryAsList(String path, boolean includeSubdirectories, List<String> excludeDirectories, String progressStateLabel) {
1765        DirectoryTree directoryTree = findAllDirectoriesInDirectory(path, includeSubdirectories, true, excludeDirectories, progressStateLabel);
1766        ArrayList<String> result = new ArrayList<String>();
1767        addDirectoryTree(result, directoryTree);
1768        return result;
1769    }
1770
1771    private static void addDirectoryTree(ArrayList<String> paths, DirectoryTree parentDirectoryTree) {
1772        if (parentDirectoryTree == null) {
1773            return ;
1774        }
1775
1776        if (parentDirectoryTree.subdirectories == null) {
1777            return ;
1778        }
1779
1780        Iterator<DirectoryTree> i = parentDirectoryTree.subdirectories.iterator();
1781        while (i.hasNext()) {
1782            DirectoryTree directoryTree = i.next();
1783            paths.add(directoryTree.fullName);
1784            addDirectoryTree(paths, directoryTree);
1785        }
1786    }
1787
1788    public static DirectoryTree findAllDirectoriesInDirectory(String path, boolean includeSubdirectories) {
1789        return findAllDirectoriesInDirectory(path, includeSubdirectories, true, null);
1790    }
1791
1792    public static DirectoryTree findAllDirectoriesInDirectory(String path, boolean includeSubdirectories, List<String> excludeDirectories) {
1793        return findAllDirectoriesInDirectory(path, includeSubdirectories, true, excludeDirectories);
1794    }
1795
1796    public static DirectoryTree findAllDirectoriesInDirectory(String path, boolean includeSubdirectories, boolean caseSensitive, List<String> excludeDirectories) {
1797        return findAllDirectoriesInDirectory(path, includeSubdirectories, caseSensitive, excludeDirectories, null);
1798    }
1799
1800    public static DirectoryTree findAllDirectoriesInDirectory(String path, boolean includeSubdirectories, boolean caseSensitive, List<String> excludeDirectories, String progressStateLabel) {
1801        ProgressState progressState = ProgressThreadData.createProgressState();
1802        if (progressState != null) {
1803            if (progressStateLabel == null) {
1804                progressState.setName("Finding directories in directory " + path);
1805            } else {
1806                progressState.setName(progressStateLabel);
1807            }
1808        }
1809        FilterData filterData = calculateFilter(path, caseSensitive);
1810        path = filterData.path;
1811        File file = new File(path);
1812        DirectoryTree result = null;
1813        if (file.exists()) {
1814            result = findAllDirectoriesInDirectory(path, filterData.filter, includeSubdirectories, caseSensitive, excludeDirectories, progressState);
1815        }
1816        ProgressThreadData.disposeProgressState(progressState);
1817        return result;
1818    }
1819
1820    private static DirectoryTree findAllDirectoriesInDirectory(String path, String filter, boolean includeSubdirectories, boolean caseSensitive, List<String> excludeDirectories, ProgressState progressState) {
1821        File file = new File(path);
1822        File[] filesArray = file.listFiles((FileFilter)null);
1823        DirectoryTree result = new DirectoryTree();
1824        result.name = calculateLastDirectoryName(path);
1825        result.fullName = path;
1826        if (filesArray == null) {
1827            return result;
1828        }
1829
1830        if (progressState != null) {
1831            progressState.setMaximum(filesArray.length);
1832        }
1833
1834        for (int i = 0; i < filesArray.length; i++) {
1835            if (progressState != null) {
1836                progressState.incCurrent();
1837            }
1838
1839            if (!filesArray[i].isDirectory()) {
1840                continue;
1841            }
1842
1843            if (!StringUtil.doesStringMatchWildcard(filesArray[i].getName(), filter, false)) {
1844                continue;
1845            }
1846
1847            if (result.subdirectories == null) {
1848                result.subdirectories = new ArrayList<DirectoryTree>();
1849            }
1850
1851            String subdirectory = filesArray[i].getAbsolutePath() + File.separator;
1852            if (excludeDirectory(excludeDirectories, subdirectory, caseSensitive)) {
1853                continue;
1854            }
1855
1856            DirectoryTree temp;
1857            if (includeSubdirectories) {
1858                ProgressState subProgressState = null;
1859                if (progressState != null) {
1860                    subProgressState = ProgressThreadData.createProgressState();
1861                    String name = calculateLastDirectoryNameNoIO(subdirectory);
1862                    subProgressState.setName(name);
1863                }
1864                temp = findAllDirectoriesInDirectory(subdirectory, filter, includeSubdirectories, caseSensitive, excludeDirectories, subProgressState);
1865                ProgressThreadData.disposeProgressState(subProgressState);
1866            } else {
1867                temp = new DirectoryTree();
1868                temp.name = calculateLastDirectoryName(subdirectory);
1869                temp.fullName = subdirectory;
1870            }
1871            result.subdirectories.add(temp);
1872        }
1873        
1874        if (result.subdirectories != null) {
1875            Collections.sort(result.subdirectories, new Comparator<DirectoryTree>() {
1876                @Override
1877                public int compare(DirectoryTree directoryTree1, DirectoryTree directoryTree2) {
1878                    return directoryTree1.fullName.compareTo(directoryTree2.fullName);
1879                }
1880            });
1881        }
1882
1883        return result;
1884    }
1885
1886    public static List<String> findAllFilesInDirectory(String path, boolean includeSubdirectories) {
1887        return findAllFilesInDirectory(path, includeSubdirectories, false);
1888    }
1889
1890    public static List<String> findAllFilesInDirectory(String path, boolean includeSubdirectories, boolean caseSensitive) {
1891        return findAllFilesInDirectory(path, includeSubdirectories, caseSensitive, null);
1892    }
1893
1894    public static List<String> findAllFilesInDirectory(String path, boolean includeSubdirectories, List<String> excludeDirectories) {
1895        return findAllFilesInDirectory(path, includeSubdirectories, false, excludeDirectories);
1896    }
1897
1898    public static List<String> findAllFilesInDirectory(String path, boolean includeSubdirectories, boolean caseSensitive, List<String> excludeDirectories) {
1899        return findAllFilesInDirectory(path, includeSubdirectories, caseSensitive, excludeDirectories, null);
1900    }
1901
1902    public static List<String> findAllFilesInDirectory(String path, boolean includeSubdirectories, boolean caseSensitive, List<String> excludeDirectories, String progressStateLabel) {
1903        if (path == null) {
1904            throw new NullPointerException("path == null");
1905        }
1906
1907        ArrayList<String> result = new ArrayList<String>();
1908        ProgressState progressState = ProgressThreadData.createProgressState();
1909        if (progressState != null) {
1910            if (progressStateLabel == null) {
1911                progressState.setName("Finding files in path " + path);
1912            } else {
1913                progressState.setName(progressStateLabel);
1914            }
1915        }
1916
1917        findAllFilesInDirectory(result, path, includeSubdirectories, caseSensitive, excludeDirectories, progressState);
1918
1919        ProgressThreadData.disposeProgressState(progressState);
1920
1921        Util.sortStringListCaseInsensitiveOrder(result);
1922
1923        return result;
1924    }
1925
1926    private static void findAllFilesInDirectory(Collection<String> files, String path, boolean includeSubdirectories, boolean caseSensitive, List<String> excludeDirectories, ProgressState progressState) {
1927        FilterData filterData = calculateFilter(path, caseSensitive);
1928
1929        List<String> absoluteExcludePaths = null;
1930        if (excludeDirectories != null) {
1931            absoluteExcludePaths = new ArrayList<String>();
1932            for (String excludePath : excludeDirectories) {
1933                if (!isAbsolute(excludePath)) {
1934                    excludePath = calculateAbsolutePath(filterData.path, excludePath);
1935                }
1936
1937                absoluteExcludePaths.add(excludePath);
1938            }
1939        }
1940
1941        findAllFilesInDirectoryLocal(files, filterData.path, filterData.filter, includeSubdirectories, caseSensitive, absoluteExcludePaths, progressState);
1942    }
1943
1944    public static class FilterData {
1945        public String path;
1946        public String filter;
1947
1948        @Override
1949        public String toString() {
1950            return path + (path.endsWith(File.separator) ? "" : File.separator) + filter;
1951        }
1952    }
1953
1954    public static FilterData calculateFilter(String path, boolean caseSensitive) {
1955        if (path == "" || path == ".") {
1956            path = "." + File.separator;
1957        }
1958        File file = new File(path);
1959        String filter = "";
1960        if (!path.endsWith(File.separator) && (!file.exists() || !file.isDirectory())) {
1961            filter = file.getName();
1962            if (file.isFile() || filter != "") {
1963                file = file.getParentFile();
1964                if (file != null) {
1965                    path = file.getPath() + File.separator;
1966                } else {
1967                    path = ".";
1968                }
1969            }
1970        }
1971        if (filter == "") {
1972            filter = "*";
1973        }
1974        if (!caseSensitive) {
1975            filter = filter.toLowerCase();
1976        }
1977
1978        FilterData filterData = new FilterData();
1979        filterData.path = path;
1980        filterData.filter = filter;
1981        return filterData;
1982    }
1983
1984    private static boolean excludeDirectory(List<String> excludeDirectories, String subdirectory, boolean caseSensitive) {
1985        if (excludeDirectories == null || excludeDirectories.size() == 0) {
1986            return false;
1987        }
1988
1989        if (!caseSensitive) {
1990            subdirectory = subdirectory.toLowerCase();
1991        }
1992
1993        Iterator<String> i2 = excludeDirectories.iterator();
1994        while (i2.hasNext()) {
1995            String excludeDirectory = i2.next();
1996            if (!caseSensitive) {
1997                excludeDirectory = excludeDirectory.toLowerCase();
1998            }
1999            if (subdirectory.startsWith(excludeDirectory)) {
2000                return true;
2001            }
2002        }
2003
2004        return false;
2005    }
2006    
2007    private static void findAllFilesInDirectoryLocal(Collection<String> files, String path, String filter, boolean includeSubdirectories, boolean caseSensitive, List<String> excludeDirectories, ProgressState progressState) {
2008        File file = new File(path);
2009        File[] filesArray = file.listFiles((FileFilter)null);
2010        if (filesArray == null) {
2011            return ;
2012        }
2013
2014        if (progressState != null) {
2015            progressState.setMaximum(filesArray.length);
2016        }
2017
2018        for (int i = 0; i < filesArray.length; i++) {
2019            if (progressState != null) {
2020                progressState.incCurrent();
2021            }
2022
2023            if (filesArray[i].isFile()) {
2024                String fileName = filesArray[i].getName();
2025                if (!caseSensitive) {
2026                    fileName = fileName.toLowerCase();
2027                }
2028                if (StringUtil.doesStringMatchWildcard(fileName, filter, false)) {
2029                    try {
2030                        String canonicalPath = filesArray[i].getCanonicalPath();
2031                        files.add(canonicalPath);
2032                    } catch (IOException e) {
2033                        throw new RuntimeException(e);
2034                    }
2035                }
2036            }
2037
2038            String subdirectory = filesArray[i].getAbsolutePath() + File.separator;
2039            if (excludeDirectory(excludeDirectories, subdirectory, caseSensitive)) {
2040                continue;
2041            }
2042
2043            if (includeSubdirectories && filesArray[i].isDirectory()) {
2044/*                if (Util.isLinuxOS() && isSymbolicLink(subdirectory)) { // TODO Remove
2045                    System.out.println("Directory \"" + subdirectory + "\" is a link => no follow");
2046                    continue;
2047                }*/
2048                
2049                ProgressState subProgressState = null;
2050                if (progressState != null) {
2051                    subProgressState = ProgressThreadData.createProgressState();
2052                    String name = calculateLastDirectoryNameNoIO(subdirectory);
2053                    subProgressState.setName(name);
2054                }
2055                findAllFilesInDirectoryLocal(files, subdirectory, filter, includeSubdirectories, caseSensitive, excludeDirectories, subProgressState);
2056                ProgressThreadData.disposeProgressState(subProgressState);
2057            }
2058        }
2059    }
2060    
2061    public static boolean isSymbolicLink(String fileName) {
2062        Path link = Paths.get(fileName);
2063        return Files.isSymbolicLink(link);
2064    }
2065
2066    public static boolean doesDirectoryContainAFile(String path) {
2067        File file = new File(path);
2068        File[] filesArray = file.listFiles((FileFilter)null);
2069        if (filesArray == null) {
2070            return false;
2071        }
2072
2073        for (int i = 0; i < filesArray.length; i++) {
2074            if (filesArray[i].isFile()) {
2075                return true;
2076            }
2077
2078            if (filesArray[i].isDirectory()) {
2079                String subdirectory = filesArray[i].getAbsolutePath() + File.separator;
2080                if (doesDirectoryContainAFile(subdirectory)) {
2081                    return true;
2082                }
2083            }
2084        }
2085
2086        return false;
2087    }
2088
2089    public static boolean compareFiles(String fileName1, String fileName2) {
2090        return compareFilesEqualsBytes(fileName1, fileName2) >= 0;
2091    }
2092    
2093    public static final long COMPARE_ERROR = -1;
2094
2095    /**
2096     * Compares the two files.
2097     * @return If the files are identical, then the number of bytes compared is returned. If the files are not identical, then the first different byte position is returned negative. On error -1 is returned.
2098     */
2099    public static long compareFilesEqualsBytes(String fileName1, String fileName2) {
2100        try {
2101            BufferedInputStream in1;
2102            try {
2103                in1 = new BufferedInputStream(new FileInputStream(fileName1));
2104            } catch (FileNotFoundException e) {
2105                return COMPARE_ERROR;
2106            }
2107
2108            BufferedInputStream in2;
2109            try {
2110                in2 = new BufferedInputStream(new FileInputStream(fileName2));
2111            } catch (FileNotFoundException e) {
2112                in1.close();
2113                return COMPARE_ERROR;
2114            }
2115
2116            long result = 0;
2117            boolean equals = true;
2118            byte[] buffer1 = new byte[10000];
2119            byte[] buffer2 = new byte[10000];
2120            ProgressState progressState = ProgressThreadData.createProgressState();
2121            if (progressState != null) {
2122                progressState.setName("Comparing files " + fileName1 + " and " + fileName2);
2123                long size = size(fileName1);
2124                progressState.setMaximum((int)(size));
2125                progressState.setUseUnits(true);
2126            }
2127            do {
2128                int byteRead1 = in1.read(buffer1);
2129                if (byteRead1 == -1) {
2130                    break;
2131                }
2132                int byteRead2 = in2.read(buffer2);
2133                if (byteRead2 == -1) {
2134                    break;
2135                }
2136                if (byteRead1 != byteRead2) {
2137                    result += Math.min(byteRead1, byteRead2);
2138                    result = -result;
2139                    break;
2140                }
2141                for (int i = 0; i < byteRead1; i++) {
2142                    if (buffer1[i] != buffer2[i]) {
2143                        result += i + 1;
2144                        result = -result;
2145                        equals = false;
2146                        break;
2147                    }
2148                }
2149                if (equals) {
2150                    result += byteRead1;
2151                    if (progressState != null) {
2152                        progressState.setCurrent(progressState.getCurrent() + byteRead1);
2153                    }
2154                }
2155            } while (equals);
2156
2157            in1.close();
2158            in2.close();
2159
2160            ProgressThreadData.disposeProgressState(progressState);
2161
2162            return result;
2163        } catch (IOException e) {
2164            return COMPARE_ERROR;
2165        }
2166    }
2167
2168    public static boolean getReadable(String fileName) {
2169        return new File(fileName).canRead();
2170    }
2171    
2172    public static void setReadable(String fileName, boolean readable) {
2173        setReadable(fileName, true);
2174    }
2175
2176    public static boolean setReadable(String fileName, boolean readable, boolean throwOnFail) {
2177        if (Util.getOSType() != OsType.LINUX && Util.getOSType() != OsType.MAC) {
2178            return true;
2179        }
2180
2181        boolean succeeded = new File(fileName).setReadable(readable, false);
2182        if (!succeeded && throwOnFail) {
2183            throw new RuntimeException("setReadable failed for file \"" + fileName + "\"");
2184        }
2185
2186        return succeeded;
2187    }
2188    
2189    public static String findExistingParent(String fileName) {
2190        fileName = calculateAbsoluteFileName(fileName);
2191        if (exists(fileName) && !isDirectory(fileName)) {
2192            return calculatePath(fileName);
2193        }
2194
2195        boolean first = true;
2196        while (!exists(fileName) && fileName.length() > 0) {
2197            if (first) {
2198                first = false;
2199                fileName = calculatePath(fileName);
2200                continue;
2201            }
2202            fileName = calculateParentPathFromPath(fileName);
2203        }
2204        if (!fileName.endsWith(File.separator)) {
2205            fileName += File.separator;
2206        }
2207        
2208        return fileName;
2209    }
2210    
2211    public static boolean getWritable(String fileName) {
2212        if (Util.isWindowsOS()) {
2213            // canWrite() will only return the read-only attribute on Windows, not whether the file actually can be written. The checkWriteAccess() method will to this, but it is slower.
2214            return checkWriteAccess(fileName);
2215        }
2216        
2217        String existingParent = findExistingParent(fileName);
2218        return new File(existingParent).canWrite();
2219    }
2220    
2221    public static void setWritable(String fileName, boolean writable) {
2222        setWritable(fileName, true);
2223    }
2224
2225    public static boolean setWritable(String fileName, boolean writable, boolean throwOnFail) {
2226        if (Util.getOSType() != OsType.LINUX && Util.getOSType() != OsType.MAC) {
2227            return true;
2228        }
2229
2230        boolean succeeded = new File(fileName).setWritable(writable, false);
2231        if (!succeeded && throwOnFail) {
2232            throw new RuntimeException("setWritable failed for file \"" + fileName + "\"");
2233        }
2234
2235        return succeeded;
2236    }
2237    
2238    public static boolean getExecutable(String fileName) {
2239        return new File(fileName).canExecute();
2240    }
2241    
2242    public static void setExecutable(String fileName, boolean executable) {
2243        setExecutable(fileName, executable, true);
2244    }
2245
2246    public static boolean setExecutable(String fileName, boolean executable, boolean throwOnFail) {
2247        if (Util.getOSType() != OsType.LINUX && Util.getOSType() != OsType.MAC) {
2248            return true;
2249        }
2250
2251        boolean succeeded = new File(fileName).setExecutable(executable, false);
2252        if (!succeeded && throwOnFail) {
2253            throw new RuntimeException("setExecutable failed for file \"" + fileName + "\"");
2254        }
2255
2256        return succeeded;
2257    }
2258
2259    public static long getFreeSpace(String path) {
2260        File file = new File(path);
2261        long freeSpace = file.getFreeSpace();
2262        if (Util.isWindowsOS()) {
2263            if (freeSpace == 0 && !file.exists()) {
2264                file = new File("." + File.separator + path);
2265                freeSpace = file.getFreeSpace();
2266            }
2267
2268            while (freeSpace == 0 && !file.exists()) {
2269                file = file.getParentFile();
2270                if (file == null) {
2271                    break;
2272                }
2273                freeSpace = file.getFreeSpace();
2274            }
2275        }
2276
2277        if (Util.isLinuxOS()) {
2278            while (freeSpace == 0 && file != null && !file.exists()) {
2279                file = file.getParentFile();
2280                if (file == null) {
2281                    break;
2282                }
2283                freeSpace = file.getFreeSpace();
2284            }
2285        }
2286
2287        return freeSpace;
2288    }
2289
2290    /**
2291     * Check for write access using the creation of an actual file.
2292     * <br>
2293     * See {@link #getWritable(String)} for details
2294     */
2295    public static boolean checkWriteAccess(String fileName) {
2296        String existingParent = findExistingParent(fileName);
2297        
2298        String tempFileName;
2299        try {
2300            tempFileName = createTempFile(existingParent, DEFAULT_TEMP_FILE_PREFIX + "FileUtil - checkWriteAccess - ", ".txt");
2301        } catch (Exception e) {
2302            return false;
2303        }
2304        
2305        if (!exists(tempFileName)) {
2306            throw new RuntimeException("The file doesn't exist, after a creation has been attempted");
2307        }
2308        
2309        delete(tempFileName);
2310        
2311        if (exists(tempFileName)) {
2312            throw new RuntimeException("The file exists after it has been attempted to be deleted");
2313        }
2314        
2315        return true;
2316    }
2317    
2318    public static class LockFile implements AutoCloseable {
2319        private String lockFileName;
2320        private FileOutputStream fileOutputStream;
2321        private FileChannel channel;
2322        private FileLock lock;
2323        private boolean valid;
2324        
2325        public LockFile(String plockFileName, FileOutputStream pfileOutputStream, FileChannel pchannel, FileLock plock) {
2326            lockFileName = plockFileName;
2327            fileOutputStream = pfileOutputStream;
2328            channel = pchannel;
2329            lock = plock;
2330            valid = true;
2331        }
2332        
2333        public synchronized void releaseLockAndDeleteLockFile() {
2334            releaseLockAndDeleteLockFile(true, null);
2335        }
2336        
2337        private synchronized void releaseLockAndDeleteLockFile(boolean check, String closedBy) {
2338            try {
2339                if (!valid) {
2340                    if (check) {
2341                        throw new RuntimeException("!valid " + this);
2342                    }
2343                    
2344                    return ;
2345                }
2346                valid = false;
2347
2348                if (closedBy != null) {
2349                    Util.printMessageWithSourceLine("Closed by: " + closedBy);
2350                }
2351                
2352                // Linux only:
2353                // If delete only is done with the lock, it seems to work. Deleting it after the lock has been closed / released breaks stuff.
2354                // NOTE: The file can be deleted without the lock!
2355                if (!Util.isWindowsOS()) {
2356                    if (exists(lockFileName)) {
2357                        delete(lockFileName);
2358                    } else {
2359                        if (check) {
2360                            throw new RuntimeException("Lock file \"" + lockFileName + "\" doesn't exist as it should");
2361                        }
2362                    }
2363                }
2364                
2365                lock.close();
2366                channel.close();
2367                fileOutputStream.close();
2368
2369                if (Util.isWindowsOS()) {
2370                    // Windows only:
2371                    // As the lock file can't be deleted with the lock, it is deleted after the lock has been released.
2372                    // This might fail if some else has taken the lock, therefore only a warning is printed.
2373                    if (exists(lockFileName)) {
2374                        boolean deleted = delete(lockFileName, false);
2375                        if (!deleted) {
2376                            System.out.println("WARNING: Deletion of lock file failed");
2377                        }
2378                    }
2379                }
2380            } catch (IOException e) {
2381                throw new RuntimeException(e);
2382            }
2383        }
2384        
2385        public boolean isValid() {
2386            return valid && exists(lockFileName);
2387        }
2388        
2389        @Override
2390        public void close() throws Exception {
2391            releaseLockAndDeleteLockFile(false, "close");
2392        }
2393        
2394        @Override
2395        protected void finalize() throws Throwable {
2396            releaseLockAndDeleteLockFile(false, "finalize");
2397        }
2398        
2399        @Override
2400        public String toString() {
2401            return "Write lock on \"" + lockFileName + "\"" + (valid ? "" : " NOT VALID");
2402        }
2403    }
2404    
2405    public static LockFile createLockFile(String lockFileName) {
2406        return createLockFile(lockFileName, true);
2407    }
2408    
2409    public static LockFile createLockFile(String lockFileName, boolean throwOnFail) {
2410        try {
2411            FileOutputStream fileOutputStream = new FileOutputStream(lockFileName);
2412            FileChannel channel = fileOutputStream.getChannel();
2413            FileLock lock = channel.tryLock();
2414            if (lock == null) {
2415                try {
2416                    fileOutputStream.close();
2417                } catch (IOException e) {
2418                    e.printStackTrace();
2419                }
2420                
2421                if (throwOnFail) {
2422                    throw new RuntimeException("Write lock on file \"" + lockFileName + "\" couldn't be achieved");
2423                }
2424                
2425                return null;
2426            }
2427            return new LockFile(lockFileName, fileOutputStream, channel, lock);
2428        } catch (OverlappingFileLockException e) {
2429            return null;
2430        } catch (IOException e) {
2431            if (throwOnFail) {
2432                throw new RuntimeException(e);
2433            }
2434            return null;
2435        }
2436    }
2437
2438    /**
2439     * Finds an available (Non existing) file name in path.
2440     * <br>
2441     * Example usage:
2442     * <pre><code>    int number = FileUtil.findAvailableFileName(path, fileName, 0);
2443     *    String newFileName = FileUtil.calculateNumberedFileName(path, fileName, number);</code></pre>
2444     * @see #createTempFile(String, String, String)
2445     * @see #calculateNumberedFileName(String, String, int)
2446     */
2447    public static int findAvailableFileName(String path, String fileName, int number) {
2448        do {
2449            number++;
2450            String tempFileName = calculateNumberedFileName(path, fileName, number);
2451            File file = new File(tempFileName);
2452            if (file.exists()) {
2453                continue;
2454            }
2455            return number;
2456        } while (true);
2457    }
2458
2459    public static void checkForTempFiles(final List<String> fileNames) {
2460        Thread thread = new Thread() {
2461            @Override
2462            public void run() {
2463                for (String fileName : fileNames) {
2464                    String absoluteFileName = new File(fileName).getAbsoluteFile().toString();
2465                    String path = calculatePath(absoluteFileName);
2466                    final String extension = calculateExtension(absoluteFileName);
2467                    String[] tempFiles = new File(path).list(new FilenameFilter() {
2468                        public boolean accept(File ppath, String name) {
2469                            if (StringUtil.doesStringMatchMultiWildcard(name, DEFAULT_TEMP_FILE_PREFIX + "* - *" + extension, false)) {
2470                                return true;
2471                            }
2472                            return false;
2473                        }
2474                    });
2475
2476                    if (tempFiles != null && tempFiles.length > 0) {
2477                        GUIUtil.showWarning("Temp files: " + Arrays.toString(tempFiles));
2478                        Util.openDirectory(path);
2479                    }
2480                }
2481            }
2482        };
2483        thread.setPriority(Thread.MIN_PRIORITY);
2484        thread.start();
2485    }
2486
2487    /**
2488     * Creates a temporary file in path.
2489     * <br>
2490     * Use the {@link #findAvailableFileName(String, String, int)} to find an available file name.
2491     * @see #findAvailableFileName(String, String, int)
2492     */
2493    public static String createTempFile(String path, String prefix, String postfix) {
2494        int count = 0;
2495        while (true) {
2496            File file = new File(path + prefix + MathUtil.randomNumber(1000000) + postfix);
2497            try {
2498                if (!file.createNewFile()) {
2499                    if (count > 100) {
2500                        throw new RuntimeException("count > 100 => createTempFile(Path: \"" + path + "\", prefix: \"" + prefix + "\", \", postfix: \"" + postfix + "\") failed");
2501                    }
2502                    
2503                    count++;
2504                    continue;
2505                }
2506            } catch (IOException e) {
2507                throw new RuntimeException("createTempFile(Path: \"" + path + "\", prefix: \"" + prefix + "\", \", postfix: \"" + postfix + "\")", e);
2508            }
2509            
2510            return file.toString();
2511        }
2512    }
2513
2514    public static String getTempPath() {
2515        String tempPath = System.getenv("TEMP");
2516        if (tempPath != null) {
2517            tempPath += File.separator;
2518        } else {
2519            tempPath = System.getenv("TMPDIR");
2520            if (tempPath == null) {
2521                tempPath = System.getenv("P_tmpdir");
2522                if (tempPath == null) {
2523                    switch (Util.getOSType()) {
2524                        case WINDOWS :
2525                            tempPath = WINDOWS_TEMP_PATH;
2526                        break;
2527                        case LINUX :
2528                            tempPath = LINUX_TEMP_PATH;
2529                        break;
2530                        case MAC :
2531                            tempPath = MAC_TEMP_PATH;
2532                        break;
2533                        default :
2534                            throw new RuntimeException("Unknown OS type: " + Util.getOSType());
2535                    }
2536                }
2537            }
2538        }
2539
2540        return tempPath;
2541    }
2542
2543    public static String currentPath() {
2544        return System.getProperty("user.dir") + File.separatorChar;
2545    }
2546
2547    public static String getInstallationPath() {
2548        switch (Util.getOSType()) {
2549            case WINDOWS :
2550                String windows7Path = System.getenv("PROGRAMFILES(X86)");
2551                if (windows7Path != null) {
2552                    return windows7Path + "\\";
2553                }
2554
2555                String windowsXPPath = System.getenv("PROGRAMFILES");
2556                if (windowsXPPath != null) {
2557                    return windowsXPPath + "\\";
2558                }
2559
2560                return "C:\\Programs\\";
2561//          break;
2562            case LINUX :
2563                return "/opt/";
2564//          break;
2565            case MAC :
2566                return "/opt/";
2567//          break;
2568            default :
2569                throw new RuntimeException("Unknown OS type: " + Util.getOSType());
2570        }
2571    }
2572
2573    public static boolean fileContainsText(String fileName, String text) {
2574        return fileContainsText(fileName, text, null);
2575    }
2576
2577    public static boolean fileContainsText(String fileName, String text, String encoding) {
2578        if (!exists(fileName)) {
2579            return false;
2580        }
2581        
2582        boolean found = false;
2583        try {
2584            BufferedReader in = createBufferedReader(fileName, encoding);
2585            char[] buffer = new char[100000];
2586            int offset = 0;
2587            do {
2588                int length = in.read(buffer);
2589                if (length == -1) {
2590                    break;
2591                }
2592                for (int i = 0; i < length; i++) {
2593                    if (buffer[i] == text.charAt(offset)) {
2594                        offset++;
2595                        if (offset == text.length()) {
2596                            found = true;
2597                            break;
2598                        }
2599                    } else {
2600                        i -= offset;
2601                        if (i < 0) {
2602                            // May cause a bug, but it is too complex to fix.
2603                            i = -1;
2604                        }
2605                        offset = 0;
2606                    }
2607                }
2608/*                String string = new String(buffer, 0, length);
2609                if (string.contains(text)) {
2610                    found = true;
2611                }*/
2612            } while (!found);
2613            in.close();
2614        } catch (Exception e) {
2615            throw new RuntimeException(e);
2616        }
2617
2618        return found;
2619    }
2620    
2621    /**
2622     * Loads properties from a file and saves them into System properties ({@link java.lang.System#setProperty(String, String)}.
2623     * <br>
2624     * Note: This does NOT overwrite properties that are already set.
2625     */
2626    public static void loadPropertiesFromFile(String fileName) {
2627        
2628        FileInputStream stream = null;
2629        try {
2630            stream = new FileInputStream(fileName);
2631            Properties props = new Properties();
2632            props.load(stream);
2633            Enumeration<?> properties = props.propertyNames();
2634            while (properties.hasMoreElements()) {
2635                String property = (String)properties.nextElement();
2636                if (System.getProperty(property, null) == null) {
2637                    String value = (String)props.get(property);
2638                    System.setProperty(property, value);
2639                }
2640            }
2641        } catch (FileNotFoundException e) {
2642            throw new RuntimeException(e);
2643        } catch (Exception e) {
2644            throw new RuntimeException(e);
2645        } finally {
2646            if (stream != null) {
2647                try {
2648                    stream.close();
2649                } catch (IOException e) {
2650                    throw new RuntimeException(e);
2651                }
2652            }
2653        }
2654    }
2655
2656    public static Properties loadPropertyFile(String fileName) {
2657        return loadPropertyFile(fileName, null);
2658    }
2659    
2660    public static Properties loadPropertyFile(String fileName, String encoding) {
2661        BufferedReader bufferedReader = createBufferedReader(fileName, encoding);
2662        Properties properties = new Properties();
2663        try {
2664            properties.load(bufferedReader);
2665            bufferedReader.close();
2666        } catch (IOException e) {
2667            throw new RuntimeException(e);
2668        }
2669        return properties;
2670    }
2671}