CR-IC-7232 (formatting)
[idea/community.git] / platform / util-rt / src / com / intellij / openapi / util / io / FileUtilRt.java
1 /*
2  * Copyright 2000-2015 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, 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, char separator, boolean caseSensitive) {
213     basePath = ensureEnds(basePath, separator);
214
215     if (caseSensitive ? basePath.equals(ensureEnds(filePath, separator)) : basePath.equalsIgnoreCase(ensureEnds(filePath, separator))) {
216       return ".";
217     }
218
219     int len = 0;
220     int lastSeparatorIndex = 0; // need this for cases like this: base="/temp/abc/base" and file="/temp/ab"
221     CharComparingStrategy strategy = caseSensitive ? CharComparingStrategy.IDENTITY : CharComparingStrategy.CASE_INSENSITIVE;
222     while (len < filePath.length() && len < basePath.length() && strategy.charsEqual(filePath.charAt(len), basePath.charAt(len))) {
223       if (basePath.charAt(len) == separator) {
224         lastSeparatorIndex = len;
225       }
226       len++;
227     }
228
229     if (len == 0) return null;
230
231     StringBuilder relativePath = new StringBuilder();
232     for (int i = len; i < basePath.length(); i++) {
233       if (basePath.charAt(i) == separator) {
234         relativePath.append("..");
235         relativePath.append(separator);
236       }
237     }
238     relativePath.append(filePath.substring(lastSeparatorIndex + 1));
239
240     return relativePath.toString();
241   }
242
243   private static String ensureEnds(@NotNull String s, final char endsWith) {
244     return StringUtilRt.endsWithChar(s, endsWith) ? s : s + endsWith;
245   }
246
247   @NotNull
248   public static String getNameWithoutExtension(@NotNull String name) {
249     int i = name.lastIndexOf('.');
250     if (i != -1) {
251       name = name.substring(0, i);
252     }
253     return name;
254   }
255
256   @NotNull
257   public static File createTempDirectory(@NotNull @NonNls String prefix, @Nullable @NonNls String suffix) throws IOException {
258     return createTempDirectory(prefix, suffix, true);
259   }
260
261   @NotNull
262   public static File createTempDirectory(@NotNull @NonNls String prefix, @Nullable @NonNls String suffix, boolean deleteOnExit) throws IOException {
263     final File dir = new File(getTempDirectory());
264     return createTempDirectory(dir, prefix, suffix, deleteOnExit);
265   }
266
267   @NotNull
268   public static File createTempDirectory(@NotNull File dir,
269                                          @NotNull @NonNls String prefix, @Nullable @NonNls String suffix) throws IOException {
270     return createTempDirectory(dir, prefix, suffix, true);
271   }
272
273   @NotNull
274   public static File createTempDirectory(@NotNull File dir,
275                                          @NotNull @NonNls String prefix, @Nullable @NonNls String suffix,
276                                          boolean deleteOnExit) throws IOException {
277     File file = doCreateTempFile(dir, prefix, suffix, true);
278     if (deleteOnExit) {
279       file.deleteOnExit();
280     }
281     if (!file.isDirectory()) {
282       throw new IOException("Cannot create directory: " + file);
283     }
284     return file;
285   }
286
287   @NotNull
288   public static File createTempFile(@NotNull @NonNls String prefix, @Nullable @NonNls String suffix) throws IOException {
289     return createTempFile(prefix, suffix, false); //false until TeamCity fixes its plugin
290   }
291
292   @NotNull
293   public static File createTempFile(@NotNull @NonNls String prefix, @Nullable @NonNls String suffix,
294                                     boolean deleteOnExit) throws IOException {
295     final File dir = new File(getTempDirectory());
296     return createTempFile(dir, prefix, suffix, true, deleteOnExit);
297   }
298
299   @NotNull
300   public static File createTempFile(@NonNls File dir,
301                                     @NotNull @NonNls String prefix, @Nullable @NonNls String suffix) throws IOException {
302     return createTempFile(dir, prefix, suffix, true, true);
303   }
304
305   @NotNull
306   public static File createTempFile(@NonNls File dir,
307                                     @NotNull @NonNls String prefix, @Nullable @NonNls String suffix,
308                                     boolean create) throws IOException {
309     return createTempFile(dir, prefix, suffix, create, true);
310   }
311
312   @NotNull
313   public static File createTempFile(@NonNls File dir,
314                                     @NotNull @NonNls String prefix, @Nullable @NonNls String suffix,
315                                     boolean create, boolean deleteOnExit) throws IOException {
316     File file = doCreateTempFile(dir, prefix, suffix, false);
317     if (deleteOnExit) {
318       file.deleteOnExit();
319     }
320     if (!create) {
321       if (!file.delete() && file.exists()) {
322         throw new IOException("Cannot delete file: " + file);
323       }
324     }
325     return file;
326   }
327
328   @NotNull
329   private static File doCreateTempFile(@NotNull File dir,
330                                        @NotNull @NonNls String prefix,
331                                        @Nullable @NonNls String suffix,
332                                        boolean isDirectory) throws IOException {
333     //noinspection ResultOfMethodCallIgnored
334     dir.mkdirs();
335
336     if (prefix.length() < 3) {
337       prefix = (prefix + "___").substring(0, 3);
338     }
339     if (suffix == null) {
340       suffix = ".tmp";
341     }
342
343     int exceptionsCount = 0;
344     while (true) {
345       try {
346         final File temp = createTemp(prefix, suffix, dir, isDirectory);
347         return normalizeFile(temp);
348       }
349       catch (IOException e) { // Win32 createFileExclusively access denied
350         if (++exceptionsCount >= 100) {
351           throw e;
352         }
353       }
354     }
355   }
356
357   @NotNull
358   private static File createTemp(@NotNull String prefix, @NotNull String suffix, @NotNull File directory, boolean isDirectory) throws IOException {
359     // normalize and use only the file name from the prefix
360     prefix = new File(prefix).getName();
361
362     File f;
363     int i = 0;
364     do {
365       String name = prefix + i + suffix;
366       f = new File(directory, name);
367       if (!name.equals(f.getName())) {
368         throw new IOException("Unable to create temporary file " + f + " for name " + name);
369       }
370       i++;
371     }
372     while (f.exists());
373
374     boolean success = isDirectory ? f.mkdir() : f.createNewFile();
375     if (!success) {
376       throw new IOException("Unable to create temporary file " + f);
377     }
378
379     return f;
380   }
381
382   @NotNull
383   private static File normalizeFile(@NotNull File temp) throws IOException {
384     final File canonical = temp.getCanonicalFile();
385     return SystemInfoRt.isWindows && canonical.getAbsolutePath().contains(" ") ? temp.getAbsoluteFile() : canonical;
386   }
387
388   @NotNull
389   public static String getTempDirectory() {
390     if (ourCanonicalTempPathCache == null) {
391       ourCanonicalTempPathCache = calcCanonicalTempPath();
392     }
393     return ourCanonicalTempPathCache;
394   }
395
396   @NotNull
397   private static String calcCanonicalTempPath() {
398     final File file = new File(System.getProperty("java.io.tmpdir"));
399     try {
400       final String canonical = file.getCanonicalPath();
401       if (!SystemInfoRt.isWindows || !canonical.contains(" ")) {
402         return canonical;
403       }
404     }
405     catch (IOException ignore) { }
406     return file.getAbsolutePath();
407   }
408
409   @TestOnly
410   public static void resetCanonicalTempPathCache(final String tempPath) {
411     ourCanonicalTempPathCache = tempPath;
412   }
413
414   @NotNull
415   public static File generateRandomTemporaryPath() throws IOException {
416     File file = new File(getTempDirectory(), UUID.randomUUID().toString());
417     int i = 0;
418     while (file.exists() && i < 5) {
419       file = new File(getTempDirectory(), UUID.randomUUID().toString());
420       ++i;
421     }
422     if (file.exists()) {
423       throw new IOException("Couldn't generate unique random path.");
424     }
425     return normalizeFile(file);
426   }
427
428   /**
429    * Set executable attribute, it makes sense only on non-windows platforms.
430    *
431    * @param path           the path to use
432    * @param executableFlag new value of executable attribute
433    * @throws java.io.IOException if there is a problem with setting the flag
434    */
435   public static void setExecutableAttribute(@NotNull String path, boolean executableFlag) throws IOException {
436     final File file = new File(path);
437     if (!file.setExecutable(executableFlag) && file.canExecute() != executableFlag) {
438       LOG.warn("Can't set executable attribute of '" + path + "' to " + executableFlag);
439     }
440   }
441
442   @NotNull
443   public static String loadFile(@NotNull File file) throws IOException {
444     return loadFile(file, null, false);
445   }
446
447   @NotNull
448   public static String loadFile(@NotNull File file, boolean convertLineSeparators) throws IOException {
449     return loadFile(file, null, convertLineSeparators);
450   }
451
452   @NotNull
453   public static String loadFile(@NotNull File file, @Nullable @NonNls String encoding) throws IOException {
454     return loadFile(file, encoding, false);
455   }
456
457   @NotNull
458   public static String loadFile(@NotNull File file, @Nullable @NonNls String encoding, boolean convertLineSeparators) throws IOException {
459     final String s = new String(loadFileText(file, encoding));
460     return convertLineSeparators ? StringUtilRt.convertLineSeparators(s) : s;
461   }
462
463   @NotNull
464   public static char[] loadFileText(@NotNull File file) throws IOException {
465     return loadFileText(file, (String)null);
466   }
467
468   @NotNull
469   public static char[] loadFileText(@NotNull File file, @Nullable @NonNls String encoding) throws IOException {
470     InputStream stream = new FileInputStream(file);
471     @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
472     Reader reader = encoding == null ? new InputStreamReader(stream) : new InputStreamReader(stream, encoding);
473     try {
474       return loadText(reader, (int)file.length());
475     }
476     finally {
477       reader.close();
478     }
479   }
480   @NotNull
481   public static char[] loadFileText(@NotNull File file, @NotNull @NonNls Charset encoding) throws IOException {
482     Reader reader = new InputStreamReader(new FileInputStream(file), encoding);
483     try {
484       return loadText(reader, (int)file.length());
485     }
486     finally {
487       reader.close();
488     }
489   }
490
491   @NotNull
492   public static char[] loadText(@NotNull Reader reader, int length) throws IOException {
493     char[] chars = new char[length];
494     int count = 0;
495     while (count < chars.length) {
496       int n = reader.read(chars, count, chars.length - count);
497       if (n <= 0) break;
498       count += n;
499     }
500     if (count == chars.length) {
501       return chars;
502     }
503     else {
504       char[] newChars = new char[count];
505       System.arraycopy(chars, 0, newChars, 0, count);
506       return newChars;
507     }
508   }
509
510   @NotNull
511   public static List<String> loadLines(@NotNull File file) throws IOException {
512     return loadLines(file.getPath());
513   }
514
515   @NotNull
516   public static List<String> loadLines(@NotNull File file, @Nullable @NonNls String encoding) throws IOException {
517     return loadLines(file.getPath(), encoding);
518   }
519
520   @NotNull
521   public static List<String> loadLines(@NotNull String path) throws IOException {
522     return loadLines(path, null);
523   }
524
525   @NotNull
526   public static List<String> loadLines(@NotNull String path, @Nullable @NonNls String encoding) throws IOException {
527     InputStream stream = new FileInputStream(path);
528     try {
529       @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
530       InputStreamReader in = encoding == null ? new InputStreamReader(stream) : new InputStreamReader(stream, encoding);
531       BufferedReader reader = new BufferedReader(in);
532       try {
533         return loadLines(reader);
534       }
535       finally {
536         reader.close();
537       }
538     }
539     finally {
540       stream.close();
541     }
542   }
543
544   @NotNull
545   public static List<String> loadLines(@NotNull BufferedReader reader) throws IOException {
546     List<String> lines = new ArrayList<String>();
547     String line;
548     while ((line = reader.readLine()) != null) {
549       lines.add(line);
550     }
551     return lines;
552   }
553
554   @NotNull
555   public static byte[] loadBytes(@NotNull InputStream stream) throws IOException {
556     ByteArrayOutputStream buffer = new ByteArrayOutputStream();
557     final byte[] bytes = BUFFER.get();
558     while (true) {
559       int n = stream.read(bytes, 0, bytes.length);
560       if (n <= 0) break;
561       buffer.write(bytes, 0, n);
562     }
563     buffer.close();
564     return buffer.toByteArray();
565   }
566
567   public static boolean isTooLarge(long len) {
568     return len > LARGE_FOR_CONTENT_LOADING;
569   }
570
571   @NotNull
572   public static byte[] loadBytes(@NotNull InputStream stream, int length) throws IOException {
573     byte[] bytes = new byte[length];
574     int count = 0;
575     while (count < length) {
576       int n = stream.read(bytes, count, length - count);
577       if (n <= 0) break;
578       count += n;
579     }
580     return bytes;
581   }
582
583   /**
584    * Get parent for the file. The method correctly
585    * processes "." and ".." in file names. The name
586    * remains relative if was relative before.
587    *
588    * @param file a file to analyze
589    * @return a parent or the null if the file has no parent.
590    */
591   @Nullable
592   public static File getParentFile(@NotNull File file) {
593     int skipCount = 0;
594     File parentFile = file;
595     while (true) {
596       parentFile = parentFile.getParentFile();
597       if (parentFile == null) {
598         return null;
599       }
600       if (".".equals(parentFile.getName())) {
601         continue;
602       }
603       if ("..".equals(parentFile.getName())) {
604         skipCount++;
605         continue;
606       }
607       if (skipCount > 0) {
608         skipCount--;
609         continue;
610       }
611       return parentFile;
612     }
613   }
614
615   /**
616    * Warning! this method is _not_ symlinks-aware. Consider using com.intellij.openapi.util.io.FileUtil.delete()
617    * @param file file or directory to delete
618    * @return true if the file did not exist or was successfully deleted 
619    */
620   public static boolean delete(@NotNull File file) {
621     if (NIO_FILE_API_AVAILABLE) {
622       return deleteRecursivelyNIO(file);
623     }
624     return deleteRecursively(file);
625   }
626
627   protected static boolean deleteRecursivelyNIO(File file) {
628     try {
629       /*
630       Files.walkFileTree(file.toPath(), new SimpleFileVisitor<Path>() {
631         @Override
632         public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
633           Files.deleteIfExists(file);
634           return FileVisitResult.CONTINUE;
635         }
636       
637         @Override
638         public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
639           Files.deleteIfExists(dir);
640           return FileVisitResult.CONTINUE;
641         }
642       });
643       */
644       final Object pathObject = ourFileToPathMethod.invoke(file);
645       ourFilesWalkMethod.invoke(null, pathObject, ourDeletionVisitor);
646     }
647     catch (InvocationTargetException e) {
648       final Throwable cause = e.getCause();
649       if (cause == null || !ourNoSuchFileExceptionClass.isInstance(cause)) {
650         LOG.info(e);
651         return false;
652       }
653     }
654     catch (Exception e) {
655       LOG.info(e);
656       return false;
657     }
658     return true;
659   }
660   
661   private static boolean deleteRecursively(@NotNull File file) {
662     File[] files = file.listFiles();
663     if (files != null) {
664       for (File child : files) {
665         if (!deleteRecursively(child)) return false;
666       }
667     }
668
669     return deleteFile(file);
670   }
671
672   public interface RepeatableIOOperation<T, E extends Throwable> {
673     @Nullable T execute(boolean lastAttempt) throws E;
674   }
675
676   @Nullable
677   public static <T, E extends Throwable> T doIOOperation(@NotNull RepeatableIOOperation<T, E> ioTask) throws E {
678     for (int i = MAX_FILE_IO_ATTEMPTS; i > 0; i--) {
679       T result = ioTask.execute(i == 1);
680       if (result != null) return result;
681
682       try {
683         //noinspection BusyWait
684         Thread.sleep(10);
685       }
686       catch (InterruptedException ignored) { }
687     }
688     return null;
689   }
690
691   protected static boolean deleteFile(@NotNull final File file) {
692     Boolean result = doIOOperation(new RepeatableIOOperation<Boolean, RuntimeException>() {
693       public Boolean execute(boolean lastAttempt) {
694         if (file.delete() || !file.exists()) return Boolean.TRUE;
695         else if (lastAttempt) return Boolean.FALSE;
696         else return null;
697       }
698     });
699     return Boolean.TRUE.equals(result);
700   }
701
702   public static boolean ensureCanCreateFile(@NotNull File file) {
703     if (file.exists()) return file.canWrite();
704     if (!createIfNotExists(file)) return false;
705     return delete(file);
706   }
707
708   public static boolean createIfNotExists(@NotNull File file) {
709     if (file.exists()) return true;
710     try {
711       if (!createParentDirs(file)) return false;
712
713       OutputStream s = new FileOutputStream(file);
714       s.close();
715       return true;
716     }
717     catch (IOException e) {
718       LOG.info(e);
719       return false;
720     }
721   }
722
723   public static boolean createParentDirs(@NotNull File file) {
724     if (!file.exists()) {
725       final File parentFile = file.getParentFile();
726       if (parentFile != null) {
727         return createDirectory(parentFile);
728       }
729     }
730     return true;
731   }
732
733   public static boolean createDirectory(@NotNull File path) {
734     return path.isDirectory() || path.mkdirs();
735   }
736
737   public static void copy(@NotNull File fromFile, @NotNull File toFile) throws IOException {
738     if (!ensureCanCreateFile(toFile)) {
739       return;
740     }
741
742     FileOutputStream fos = new FileOutputStream(toFile);
743     try {
744       FileInputStream fis = new FileInputStream(fromFile);
745       try {
746         copy(fis, fos);
747       }
748       finally {
749         fis.close();
750       }
751     }
752     finally {
753       fos.close();
754     }
755
756     long timeStamp = fromFile.lastModified();
757     if (timeStamp < 0) {
758       LOG.warn("Invalid timestamp " + timeStamp + " of '" + fromFile + "'");
759     }
760     else if (!toFile.setLastModified(timeStamp)) {
761       LOG.warn("Unable to set timestamp " + timeStamp + " to '" + toFile + "'");
762     }
763   }
764
765   public static void copy(@NotNull InputStream inputStream, @NotNull OutputStream outputStream) throws IOException {
766     if (USE_FILE_CHANNELS && inputStream instanceof FileInputStream && outputStream instanceof FileOutputStream) {
767       final FileChannel fromChannel = ((FileInputStream)inputStream).getChannel();
768       try {
769         final FileChannel toChannel = ((FileOutputStream)outputStream).getChannel();
770         try {
771           fromChannel.transferTo(0, Long.MAX_VALUE, toChannel);
772         }
773         finally {
774           toChannel.close();
775         }
776       }
777       finally {
778         fromChannel.close();
779       }
780     }
781     else {
782       final byte[] buffer = BUFFER.get();
783       while (true) {
784         int read = inputStream.read(buffer);
785         if (read < 0) break;
786         outputStream.write(buffer, 0, read);
787       }
788     }
789   }
790
791   public static int getUserFileSizeLimit() {
792     try {
793       return Integer.parseInt(System.getProperty("idea.max.intellisense.filesize")) * KILOBYTE;
794     }
795     catch (NumberFormatException e) {
796       return 2500 * KILOBYTE;
797     }
798   }
799
800   private interface CharComparingStrategy {
801     CharComparingStrategy IDENTITY = new CharComparingStrategy() {
802       @Override
803       public boolean charsEqual(char ch1, char ch2) {
804         return ch1 == ch2;
805       }
806     };
807     CharComparingStrategy CASE_INSENSITIVE = new CharComparingStrategy() {
808       @Override
809       public boolean charsEqual(char ch1, char ch2) {
810         return StringUtilRt.charsEqualIgnoreCase(ch1, ch2);
811       }
812     };
813
814     boolean charsEqual(char ch1, char ch2);
815   }
816 }