avoid leaks from failing HgConfigTest.setUp
[idea/community.git] / platform / util-rt / src / com / intellij / openapi / util / io / FileUtilRt.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.openapi.util.io;
17
18 import com.intellij.openapi.diagnostic.LoggerRt;
19 import com.intellij.openapi.util.SystemInfoRt;
20 import com.intellij.openapi.util.text.StringUtilRt;
21 import org.jetbrains.annotations.NonNls;
22 import org.jetbrains.annotations.NotNull;
23 import org.jetbrains.annotations.Nullable;
24 import org.jetbrains.annotations.TestOnly;
25
26 import java.io.*;
27 import java.lang.reflect.InvocationHandler;
28 import java.lang.reflect.InvocationTargetException;
29 import java.lang.reflect.Method;
30 import java.lang.reflect.Proxy;
31 import java.nio.channels.FileChannel;
32 import java.nio.charset.Charset;
33 import java.util.ArrayList;
34 import java.util.List;
35 import java.util.UUID;
36
37 /**
38  * Stripped-down version of {@code com.intellij.openapi.util.io.FileUtil}.
39  * Intended to use by external (out-of-IDE-process) runners and helpers so it should not contain any library dependencies.
40  *
41  * @since 12.0
42  */
43 @SuppressWarnings({"UtilityClassWithoutPrivateConstructor"})
44 public class FileUtilRt {
45   private static final int KILOBYTE = 1024;
46   public static final int MEGABYTE = KILOBYTE * KILOBYTE;
47   public static final int LARGE_FOR_CONTENT_LOADING = Math.max(20 * MEGABYTE, getUserFileSizeLimit());
48
49   private static final LoggerRt LOG = LoggerRt.getInstance("#com.intellij.openapi.util.io.FileUtilLight");
50   private static final int MAX_FILE_IO_ATTEMPTS = 10;
51   private static final boolean USE_FILE_CHANNELS = "true".equalsIgnoreCase(System.getProperty("idea.fs.useChannels"));
52
53   public static final FileFilter ALL_FILES = new FileFilter() {
54     public boolean accept(File file) {
55       return true;
56     }
57   };
58   public static final FileFilter ALL_DIRECTORIES = new FileFilter() {
59     public boolean accept(File file) {
60       return file.isDirectory();
61     }
62   };
63
64   protected static final ThreadLocal<byte[]> BUFFER = new ThreadLocal<byte[]>() {
65     @Override
66     protected byte[] initialValue() {
67       return new byte[1024 * 20];
68     }
69   };
70
71   private static String ourCanonicalTempPathCache = null;
72
73   protected static final boolean NIO_FILE_API_AVAILABLE;
74
75   // todo: replace reflection with normal code after migration to JDK 1.8
76   private static Method ourFilesDeleteIfExistsMethod;
77   private static Method ourFilesWalkMethod;
78   private static Method ourFileToPathMethod;
79   private static Object ourDeletionVisitor;
80   private static Class ourNoSuchFileExceptionClass;
81   static {
82     boolean initSuccess = false;
83     try {
84       final Class<?> pathClass = Class.forName("java.nio.file.Path");
85       final Class<?> visitorClass = Class.forName("java.nio.file.FileVisitor");
86       final Class<?> filesClass = Class.forName("java.nio.file.Files");
87       ourNoSuchFileExceptionClass = Class.forName("java.nio.file.NoSuchFileException");
88
89       ourFileToPathMethod = Class.forName("java.io.File").getMethod("toPath");
90       ourFilesWalkMethod = filesClass.getMethod("walkFileTree", pathClass, visitorClass);
91       ourFilesDeleteIfExistsMethod = filesClass.getMethod("deleteIfExists", pathClass);
92       final Class<?> fileVisitResultClass = Class.forName("java.nio.file.FileVisitResult");
93       final Object Result_Continue = fileVisitResultClass.getDeclaredField("CONTINUE").get(null);
94       final Object Result_Terminate = fileVisitResultClass.getDeclaredField("TERMINATE").get(null);
95       ourDeletionVisitor = Proxy.newProxyInstance(FileUtilRt.class.getClassLoader(), new Class[]{visitorClass}, new InvocationHandler() {
96         @Override
97         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
98           if (args.length == 2) {
99             final Object second = args[1];
100             if (second instanceof Throwable) {
101               throw (Throwable)second;
102             }
103             final String methodName = method.getName();
104             if ("visitFile".equals(methodName) || "postVisitDirectory".equals(methodName)) {
105               if (!performDelete(args[0])) {
106                 return Result_Terminate;
107               }
108             }
109           }
110           return Result_Continue;
111         }
112
113         private boolean performDelete(@NotNull final Object fileObject) {
114           Boolean result = doIOOperation(new RepeatableIOOperation<Boolean, RuntimeException>() {
115             public Boolean execute(boolean lastAttempt) {
116               try {
117                 //Files.deleteIfExists(file);
118                 ourFilesDeleteIfExistsMethod.invoke(null, fileObject);
119                 return Boolean.TRUE;
120               }
121               catch (InvocationTargetException e) {
122                 if (!(e.getCause() instanceof IOException)) {
123                   return Boolean.FALSE;
124                 }
125               }
126               catch (IllegalAccessException e) {
127                 return Boolean.FALSE;
128               }
129               return lastAttempt? Boolean.FALSE : null;
130             }
131           });
132           return Boolean.TRUE.equals(result);
133         }
134
135       });
136       initSuccess = true;
137     }
138     catch (Throwable ignored) {
139       LOG.info("Was not able to detect NIO API");
140       ourFileToPathMethod = null;
141       ourFilesWalkMethod = null;
142       ourFilesDeleteIfExistsMethod = null;
143       ourDeletionVisitor = null;
144       ourNoSuchFileExceptionClass = null;
145     }
146     NIO_FILE_API_AVAILABLE = initSuccess;
147   }
148
149
150   @NotNull
151   public static String getExtension(@NotNull String fileName) {
152     int index = fileName.lastIndexOf('.');
153     if (index < 0) return "";
154     return fileName.substring(index + 1);
155   }
156
157   @NotNull
158   public static CharSequence getExtension(@NotNull CharSequence fileName) {
159     int index = StringUtilRt.lastIndexOf(fileName, '.', 0, fileName.length());
160     if (index < 0) return "";
161     return fileName.subSequence(index + 1, fileName.length());
162   }
163
164   public static boolean extensionEquals(@NotNull String fileName, @NotNull String extension) {
165     int extLen = extension.length();
166     if (extLen == 0) {
167       return fileName.indexOf('.') == -1;
168     }
169     int extStart = fileName.length() - extLen;
170     return extStart >= 1 && fileName.charAt(extStart-1) == '.'
171            && fileName.regionMatches(!SystemInfoRt.isFileSystemCaseSensitive, extStart, extension, 0, extLen);
172   }
173
174   @NotNull
175   public static String toSystemDependentName(@NonNls @NotNull String fileName) {
176     return toSystemDependentName(fileName, File.separatorChar);
177   }
178
179   @NotNull
180   public static String toSystemDependentName(@NonNls @NotNull String fileName, final char separatorChar) {
181     return fileName.replace('/', separatorChar).replace('\\', separatorChar);
182   }
183
184   @NotNull
185   public static String toSystemIndependentName(@NonNls @NotNull String fileName) {
186     return fileName.replace('\\', '/');
187   }
188
189   @Nullable
190   public static String getRelativePath(File base, File file) {
191     if (base == null || file == null) return null;
192
193     if (!base.isDirectory()) {
194       base = base.getParentFile();
195       if (base == null) return null;
196     }
197
198     //noinspection FileEqualsUsage
199     if (base.equals(file)) return ".";
200
201     final String filePath = file.getAbsolutePath();
202     String basePath = base.getAbsolutePath();
203     return getRelativePath(basePath, filePath, File.separatorChar);
204   }
205
206   @Nullable
207   public static String getRelativePath(@NotNull String basePath, @NotNull String filePath, final char separator) {
208     return getRelativePath(basePath, filePath, separator, SystemInfoRt.isFileSystemCaseSensitive);
209   }
210
211   @Nullable
212   public static String getRelativePath(@NotNull String basePath, @NotNull String filePath, final char separator, final boolean caseSensitive) {
213     basePath = ensureEnds(basePath, separator);
214
215     if (caseSensitive? basePath.equals(ensureEnds(filePath, separator)) : basePath.equalsIgnoreCase(ensureEnds(filePath, separator))) return ".";
216
217     int len = 0;
218     int lastSeparatorIndex = 0; // need this for cases like this: base="/temp/abc/base" and file="/temp/ab"
219     final CharComparingStrategy charComparingStrategy = caseSensitive? CharComparingStrategy.IDENTITY : CharComparingStrategy.CASE_INSENSITIVE;
220     while (len < filePath.length() && len < basePath.length() && charComparingStrategy.charsEqual(filePath.charAt(len), basePath.charAt(len))) {
221       if (basePath.charAt(len) == separator) {
222         lastSeparatorIndex = len;
223       }
224       len++;
225     }
226
227     if (len == 0) return null;
228
229     StringBuilder relativePath = new StringBuilder();
230     for (int i = len; i < basePath.length(); i++) {
231       if (basePath.charAt(i) == separator) {
232         relativePath.append("..");
233         relativePath.append(separator);
234       }
235     }
236     relativePath.append(filePath.substring(lastSeparatorIndex + 1));
237
238     return relativePath.toString();
239   }
240
241   private static String ensureEnds(@NotNull String s, final char endsWith) {
242     return StringUtilRt.endsWithChar(s, endsWith) ? s : s + endsWith;
243   }
244
245   @NotNull
246   public static String getNameWithoutExtension(@NotNull String name) {
247     int i = name.lastIndexOf('.');
248     if (i != -1) {
249       name = name.substring(0, i);
250     }
251     return name;
252   }
253
254   @NotNull
255   public static File createTempDirectory(@NotNull @NonNls String prefix, @Nullable @NonNls String suffix) throws IOException {
256     return createTempDirectory(prefix, suffix, true);
257   }
258
259   @NotNull
260   public static File createTempDirectory(@NotNull @NonNls String prefix, @Nullable @NonNls String suffix, boolean deleteOnExit) throws IOException {
261     final File dir = new File(getTempDirectory());
262     return createTempDirectory(dir, prefix, suffix, deleteOnExit);
263   }
264
265   @NotNull
266   public static File createTempDirectory(@NotNull File dir,
267                                          @NotNull @NonNls String prefix, @Nullable @NonNls String suffix) throws IOException {
268     return createTempDirectory(dir, prefix, suffix, true);
269   }
270
271   @NotNull
272   public static File createTempDirectory(@NotNull File dir,
273                                          @NotNull @NonNls String prefix, @Nullable @NonNls String suffix,
274                                          boolean deleteOnExit) throws IOException {
275     File file = doCreateTempFile(dir, prefix, suffix, true);
276     if (deleteOnExit) {
277       file.deleteOnExit();
278     }
279     if (!file.isDirectory()) {
280       throw new IOException("Cannot create directory: " + file);
281     }
282     return file;
283   }
284
285   @NotNull
286   public static File createTempFile(@NotNull @NonNls String prefix, @Nullable @NonNls String suffix) throws IOException {
287     return createTempFile(prefix, suffix, false); //false until TeamCity fixes its plugin
288   }
289
290   @NotNull
291   public static File createTempFile(@NotNull @NonNls String prefix, @Nullable @NonNls String suffix,
292                                     boolean deleteOnExit) throws IOException {
293     final File dir = new File(getTempDirectory());
294     return createTempFile(dir, prefix, suffix, true, deleteOnExit);
295   }
296
297   @NotNull
298   public static File createTempFile(@NonNls File dir,
299                                     @NotNull @NonNls String prefix, @Nullable @NonNls String suffix) throws IOException {
300     return createTempFile(dir, prefix, suffix, true, true);
301   }
302
303   @NotNull
304   public static File createTempFile(@NonNls File dir,
305                                     @NotNull @NonNls String prefix, @Nullable @NonNls String suffix,
306                                     boolean create) throws IOException {
307     return createTempFile(dir, prefix, suffix, create, true);
308   }
309
310   @NotNull
311   public static File createTempFile(@NonNls File dir,
312                                     @NotNull @NonNls String prefix, @Nullable @NonNls String suffix,
313                                     boolean create, boolean deleteOnExit) throws IOException {
314     File file = doCreateTempFile(dir, prefix, suffix, false);
315     if (deleteOnExit) {
316       file.deleteOnExit();
317     }
318     if (!create) {
319       if (!file.delete() && file.exists()) {
320         throw new IOException("Cannot delete file: " + file);
321       }
322     }
323     return file;
324   }
325
326   @NotNull
327   private static File doCreateTempFile(@NotNull File dir,
328                                        @NotNull @NonNls String prefix,
329                                        @Nullable @NonNls String suffix,
330                                        boolean isDirectory) throws IOException {
331     //noinspection ResultOfMethodCallIgnored
332     dir.mkdirs();
333
334     if (prefix.length() < 3) {
335       prefix = (prefix + "___").substring(0, 3);
336     }
337     if (suffix == null) {
338       suffix = ".tmp";
339     }
340
341     int exceptionsCount = 0;
342     while (true) {
343       try {
344         final File temp = createTemp(prefix, suffix, dir, isDirectory);
345         return normalizeFile(temp);
346       }
347       catch (IOException e) { // Win32 createFileExclusively access denied
348         if (++exceptionsCount >= 100) {
349           throw e;
350         }
351       }
352     }
353   }
354
355   @NotNull
356   private static File createTemp(@NotNull String prefix, @NotNull String suffix, @NotNull File directory, boolean isDirectory) throws IOException {
357     // normalize and use only the file name from the prefix
358     prefix = new File(prefix).getName();
359
360     File f;
361     int i = 0;
362     do {
363       String name = prefix + i + suffix;
364       f = new File(directory, name);
365       if (!name.equals(f.getName())) {
366         throw new IOException("Unable to create temporary file " + f + " for name " + name);
367       }
368       i++;
369     }
370     while (f.exists());
371
372     boolean success = isDirectory ? f.mkdir() : f.createNewFile();
373     if (!success) {
374       throw new IOException("Unable to create temporary file " + f);
375     }
376
377     return f;
378   }
379
380   @NotNull
381   private static File normalizeFile(@NotNull File temp) throws IOException {
382     final File canonical = temp.getCanonicalFile();
383     return SystemInfoRt.isWindows && canonical.getAbsolutePath().contains(" ") ? temp.getAbsoluteFile() : canonical;
384   }
385
386   @NotNull
387   public static String getTempDirectory() {
388     if (ourCanonicalTempPathCache == null) {
389       ourCanonicalTempPathCache = calcCanonicalTempPath();
390     }
391     return ourCanonicalTempPathCache;
392   }
393
394   @NotNull
395   private static String calcCanonicalTempPath() {
396     final File file = new File(System.getProperty("java.io.tmpdir"));
397     try {
398       final String canonical = file.getCanonicalPath();
399       if (!SystemInfoRt.isWindows || !canonical.contains(" ")) {
400         return canonical;
401       }
402     }
403     catch (IOException ignore) { }
404     return file.getAbsolutePath();
405   }
406
407   @TestOnly
408   public static void resetCanonicalTempPathCache(final String tempPath) {
409     ourCanonicalTempPathCache = tempPath;
410   }
411
412   @NotNull
413   public static File generateRandomTemporaryPath() throws IOException {
414     File file = new File(getTempDirectory(), UUID.randomUUID().toString());
415     int i = 0;
416     while (file.exists() && i < 5) {
417       file = new File(getTempDirectory(), UUID.randomUUID().toString());
418       ++i;
419     }
420     if (file.exists()) {
421       throw new IOException("Couldn't generate unique random path.");
422     }
423     return normalizeFile(file);
424   }
425
426   /**
427    * Set executable attribute, it makes sense only on non-windows platforms.
428    *
429    * @param path           the path to use
430    * @param executableFlag new value of executable attribute
431    * @throws java.io.IOException if there is a problem with setting the flag
432    */
433   public static void setExecutableAttribute(@NotNull String path, boolean executableFlag) throws IOException {
434     final File file = new File(path);
435     if (!file.setExecutable(executableFlag) && file.canExecute() != executableFlag) {
436       LOG.warn("Can't set executable attribute of '" + path + "' to " + executableFlag);
437     }
438   }
439
440   @NotNull
441   public static String loadFile(@NotNull File file) throws IOException {
442     return loadFile(file, null, false);
443   }
444
445   @NotNull
446   public static String loadFile(@NotNull File file, boolean convertLineSeparators) throws IOException {
447     return loadFile(file, null, convertLineSeparators);
448   }
449
450   @NotNull
451   public static String loadFile(@NotNull File file, @Nullable @NonNls String encoding) throws IOException {
452     return loadFile(file, encoding, false);
453   }
454
455   @NotNull
456   public static String loadFile(@NotNull File file, @Nullable @NonNls String encoding, boolean convertLineSeparators) throws IOException {
457     final String s = new String(loadFileText(file, encoding));
458     return convertLineSeparators ? StringUtilRt.convertLineSeparators(s) : s;
459   }
460
461   @NotNull
462   public static char[] loadFileText(@NotNull File file) throws IOException {
463     return loadFileText(file, (String)null);
464   }
465
466   @NotNull
467   public static char[] loadFileText(@NotNull File file, @Nullable @NonNls String encoding) throws IOException {
468     InputStream stream = new FileInputStream(file);
469     @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
470     Reader reader = encoding == null ? new InputStreamReader(stream) : new InputStreamReader(stream, encoding);
471     try {
472       return loadText(reader, (int)file.length());
473     }
474     finally {
475       reader.close();
476     }
477   }
478   @NotNull
479   public static char[] loadFileText(@NotNull File file, @NotNull @NonNls Charset encoding) throws IOException {
480     Reader reader = new InputStreamReader(new FileInputStream(file), encoding);
481     try {
482       return loadText(reader, (int)file.length());
483     }
484     finally {
485       reader.close();
486     }
487   }
488
489   @NotNull
490   public static char[] loadText(@NotNull Reader reader, int length) throws IOException {
491     char[] chars = new char[length];
492     int count = 0;
493     while (count < chars.length) {
494       int n = reader.read(chars, count, chars.length - count);
495       if (n <= 0) break;
496       count += n;
497     }
498     if (count == chars.length) {
499       return chars;
500     }
501     else {
502       char[] newChars = new char[count];
503       System.arraycopy(chars, 0, newChars, 0, count);
504       return newChars;
505     }
506   }
507
508   @NotNull
509   public static List<String> loadLines(@NotNull File file) throws IOException {
510     return loadLines(file.getPath());
511   }
512
513   @NotNull
514   public static List<String> loadLines(@NotNull File file, @Nullable @NonNls String encoding) throws IOException {
515     return loadLines(file.getPath(), encoding);
516   }
517
518   @NotNull
519   public static List<String> loadLines(@NotNull String path) throws IOException {
520     return loadLines(path, null);
521   }
522
523   @NotNull
524   public static List<String> loadLines(@NotNull String path, @Nullable @NonNls String encoding) throws IOException {
525     InputStream stream = new FileInputStream(path);
526     try {
527       @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
528       InputStreamReader in = encoding == null ? new InputStreamReader(stream) : new InputStreamReader(stream, encoding);
529       BufferedReader reader = new BufferedReader(in);
530       try {
531         return loadLines(reader);
532       }
533       finally {
534         reader.close();
535       }
536     }
537     finally {
538       stream.close();
539     }
540   }
541
542   @NotNull
543   public static List<String> loadLines(@NotNull BufferedReader reader) throws IOException {
544     List<String> lines = new ArrayList<String>();
545     String line;
546     while ((line = reader.readLine()) != null) {
547       lines.add(line);
548     }
549     return lines;
550   }
551
552   @NotNull
553   public static byte[] loadBytes(@NotNull InputStream stream) throws IOException {
554     ByteArrayOutputStream buffer = new ByteArrayOutputStream();
555     final byte[] bytes = BUFFER.get();
556     while (true) {
557       int n = stream.read(bytes, 0, bytes.length);
558       if (n <= 0) break;
559       buffer.write(bytes, 0, n);
560     }
561     buffer.close();
562     return buffer.toByteArray();
563   }
564
565   public static boolean isTooLarge(long len) {
566     return len > LARGE_FOR_CONTENT_LOADING;
567   }
568
569   @NotNull
570   public static byte[] loadBytes(@NotNull InputStream stream, int length) throws IOException {
571     byte[] bytes = new byte[length];
572     int count = 0;
573     while (count < length) {
574       int n = stream.read(bytes, count, length - count);
575       if (n <= 0) break;
576       count += n;
577     }
578     return bytes;
579   }
580
581   /**
582    * Get parent for the file. The method correctly
583    * processes "." and ".." in file names. The name
584    * remains relative if was relative before.
585    *
586    * @param file a file to analyze
587    * @return a parent or the null if the file has no parent.
588    */
589   @Nullable
590   public static File getParentFile(@NotNull File file) {
591     int skipCount = 0;
592     File parentFile = file;
593     while (true) {
594       parentFile = parentFile.getParentFile();
595       if (parentFile == null) {
596         return null;
597       }
598       if (".".equals(parentFile.getName())) {
599         continue;
600       }
601       if ("..".equals(parentFile.getName())) {
602         skipCount++;
603         continue;
604       }
605       if (skipCount > 0) {
606         skipCount--;
607         continue;
608       }
609       return parentFile;
610     }
611   }
612
613   /**
614    * Warning! this method is _not_ symlinks-aware. Consider using com.intellij.openapi.util.io.FileUtil.delete()
615    * @param file file or directory to delete
616    * @return true if the file did not exist or was successfully deleted 
617    */
618   public static boolean delete(@NotNull File file) {
619     if (NIO_FILE_API_AVAILABLE) {
620       return deleteRecursivelyNIO(file);
621     }
622     return deleteRecursively(file);
623   }
624
625   protected static boolean deleteRecursivelyNIO(File file) {
626     try {
627       /*
628       Files.walkFileTree(file.toPath(), new SimpleFileVisitor<Path>() {
629         @Override
630         public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
631           Files.deleteIfExists(file);
632           return FileVisitResult.CONTINUE;
633         }
634       
635         @Override
636         public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
637           Files.deleteIfExists(dir);
638           return FileVisitResult.CONTINUE;
639         }
640       });
641       */
642       final Object pathObject = ourFileToPathMethod.invoke(file);
643       ourFilesWalkMethod.invoke(null, pathObject, ourDeletionVisitor);
644     }
645     catch (InvocationTargetException e) {
646       final Throwable cause = e.getCause();
647       if (cause == null || !ourNoSuchFileExceptionClass.isInstance(cause)) {
648         LOG.info(e);
649         return false;
650       }
651     }
652     catch (Exception e) {
653       LOG.info(e);
654       return false;
655     }
656     return true;
657   }
658   
659   private static boolean deleteRecursively(@NotNull File file) {
660     File[] files = file.listFiles();
661     if (files != null) {
662       for (File child : files) {
663         if (!deleteRecursively(child)) return false;
664       }
665     }
666
667     return deleteFile(file);
668   }
669
670   public interface RepeatableIOOperation<T, E extends Throwable> {
671     @Nullable T execute(boolean lastAttempt) throws E;
672   }
673
674   @Nullable
675   public static <T, E extends Throwable> T doIOOperation(@NotNull RepeatableIOOperation<T, E> ioTask) throws E {
676     for (int i = MAX_FILE_IO_ATTEMPTS; i > 0; i--) {
677       T result = ioTask.execute(i == 1);
678       if (result != null) return result;
679
680       try {
681         //noinspection BusyWait
682         Thread.sleep(10);
683       }
684       catch (InterruptedException ignored) { }
685     }
686     return null;
687   }
688
689   protected static boolean deleteFile(@NotNull final File file) {
690     Boolean result = doIOOperation(new RepeatableIOOperation<Boolean, RuntimeException>() {
691       public Boolean execute(boolean lastAttempt) {
692         if (file.delete() || !file.exists()) return Boolean.TRUE;
693         else if (lastAttempt) return Boolean.FALSE;
694         else return null;
695       }
696     });
697     return Boolean.TRUE.equals(result);
698   }
699
700   public static boolean ensureCanCreateFile(@NotNull File file) {
701     if (file.exists()) return file.canWrite();
702     if (!createIfNotExists(file)) return false;
703     return delete(file);
704   }
705
706   public static boolean createIfNotExists(@NotNull File file) {
707     if (file.exists()) return true;
708     try {
709       if (!createParentDirs(file)) return false;
710
711       OutputStream s = new FileOutputStream(file);
712       s.close();
713       return true;
714     }
715     catch (IOException e) {
716       LOG.info(e);
717       return false;
718     }
719   }
720
721   public static boolean createParentDirs(@NotNull File file) {
722     if (!file.exists()) {
723       final File parentFile = file.getParentFile();
724       if (parentFile != null) {
725         return createDirectory(parentFile);
726       }
727     }
728     return true;
729   }
730
731   public static boolean createDirectory(@NotNull File path) {
732     return path.isDirectory() || path.mkdirs();
733   }
734
735   public static void copy(@NotNull File fromFile, @NotNull File toFile) throws IOException {
736     if (!ensureCanCreateFile(toFile)) {
737       return;
738     }
739
740     FileOutputStream fos = new FileOutputStream(toFile);
741     try {
742       FileInputStream fis = new FileInputStream(fromFile);
743       try {
744         copy(fis, fos);
745       }
746       finally {
747         fis.close();
748       }
749     }
750     finally {
751       fos.close();
752     }
753
754     long timeStamp = fromFile.lastModified();
755     if (timeStamp < 0) {
756       LOG.warn("Invalid timestamp " + timeStamp + " of '" + fromFile + "'");
757     }
758     else if (!toFile.setLastModified(timeStamp)) {
759       LOG.warn("Unable to set timestamp " + timeStamp + " to '" + toFile + "'");
760     }
761   }
762
763   public static void copy(@NotNull InputStream inputStream, @NotNull OutputStream outputStream) throws IOException {
764     if (USE_FILE_CHANNELS && inputStream instanceof FileInputStream && outputStream instanceof FileOutputStream) {
765       final FileChannel fromChannel = ((FileInputStream)inputStream).getChannel();
766       try {
767         final FileChannel toChannel = ((FileOutputStream)outputStream).getChannel();
768         try {
769           fromChannel.transferTo(0, Long.MAX_VALUE, toChannel);
770         }
771         finally {
772           toChannel.close();
773         }
774       }
775       finally {
776         fromChannel.close();
777       }
778     }
779     else {
780       final byte[] buffer = BUFFER.get();
781       while (true) {
782         int read = inputStream.read(buffer);
783         if (read < 0) break;
784         outputStream.write(buffer, 0, read);
785       }
786     }
787   }
788
789   public static int getUserFileSizeLimit() {
790     try {
791       return Integer.parseInt(System.getProperty("idea.max.intellisense.filesize")) * KILOBYTE;
792     }
793     catch (NumberFormatException e) {
794       return 2500 * KILOBYTE;
795     }
796   }
797
798   private interface CharComparingStrategy {
799     CharComparingStrategy IDENTITY = new CharComparingStrategy() {
800       @Override
801       public boolean charsEqual(char ch1, char ch2) {
802         return ch1 == ch2;
803       }
804     };
805     CharComparingStrategy CASE_INSENSITIVE = new CharComparingStrategy() {
806       @Override
807       public boolean charsEqual(char ch1, char ch2) {
808         return StringUtilRt.charsEqualIgnoreCase(ch1, ch2);
809       }
810     };
811
812     boolean charsEqual(char ch1, char ch2);
813   }
814 }