3ed50898fc01098a91da1e9c31bdf0bf2b26f4d0
[idea/community.git] / platform / util / src / com / intellij / openapi / util / io / FileUtil.java
1 /*
2  * Copyright 2000-2012 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
17 package com.intellij.openapi.util.io;
18
19 import com.intellij.CommonBundle;
20 import com.intellij.Patches;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.util.SystemInfo;
23 import com.intellij.openapi.util.text.StringUtil;
24 import com.intellij.util.Processor;
25 import com.intellij.util.SystemProperties;
26 import com.intellij.util.containers.ContainerUtil;
27 import com.intellij.util.containers.Stack;
28 import com.intellij.util.io.URLUtil;
29 import org.intellij.lang.annotations.RegExp;
30 import org.jetbrains.annotations.NonNls;
31 import org.jetbrains.annotations.NotNull;
32 import org.jetbrains.annotations.Nullable;
33 import org.jetbrains.annotations.TestOnly;
34
35 import java.io.*;
36 import java.lang.reflect.Method;
37 import java.nio.channels.FileChannel;
38 import java.util.*;
39 import java.util.regex.Pattern;
40
41 @SuppressWarnings({"UtilityClassWithoutPrivateConstructor"})
42 public class FileUtil {
43   public static final int MEGABYTE = 1024 * 1024;
44   public static final String ASYNC_DELETE_EXTENSION = ".__del__";
45
46   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.io.FileUtil");
47
48   private static final ThreadLocal<byte[]> BUFFER = new ThreadLocal<byte[]>() {
49     protected byte[] initialValue() {
50       return new byte[1024 * 20];
51     }
52   };
53
54   // do not use channels to copy files larger than 5 Mb because of possible MapFailed error
55   private static final long CHANNELS_COPYING_LIMIT = 5L * MEGABYTE;
56   private static String ourCanonicalTempPathCache = null;
57   private static final int MAX_FILE_DELETE_ATTEMPTS = 10;
58
59   @Nullable
60   public static String getRelativePath(File base, File file) {
61     if (base == null || file == null) return null;
62
63     if (!base.isDirectory()) {
64       base = base.getParentFile();
65       if (base == null) return null;
66     }
67
68     if (base.equals(file)) return ".";
69
70     final String filePath = file.getAbsolutePath();
71     String basePath = base.getAbsolutePath();
72     return getRelativePath(basePath, filePath, File.separatorChar);
73   }
74
75   public static String getRelativePath(@NotNull String basePath, @NotNull String filePath, final char separator) {
76     return getRelativePath(basePath, filePath, separator, SystemInfo.isFileSystemCaseSensitive);
77   }
78
79   private static String ensureEnds(@NotNull String s, final char endsWith) {
80     return StringUtil.endsWithChar(s, endsWith) ? s : s + endsWith;
81   }
82
83   public static String getRelativePath(@NotNull String basePath,
84                                        @NotNull String filePath,
85                                        final char separator,
86                                        final boolean caseSensitive) {
87     basePath = ensureEnds(basePath, separator);
88
89     String basePathToCompare = caseSensitive ? basePath : basePath.toLowerCase();
90     String filePathToCompare = caseSensitive ? filePath : filePath.toLowerCase();
91     if (basePathToCompare.equals(ensureEnds(filePathToCompare, separator))) return ".";
92     int len = 0;
93     int lastSeparatorIndex = 0; // need this for cases like this: base="/temp/abcde/base" and file="/temp/ab"
94     while (len < filePath.length() && len < basePath.length() && filePathToCompare.charAt(len) == basePathToCompare.charAt(len)) {
95       if (basePath.charAt(len) == separator) {
96         lastSeparatorIndex = len;
97       }
98       len++;
99     }
100
101     if (len == 0) return null;
102
103     StringBuilder relativePath = new StringBuilder();
104     for (int i = len; i < basePath.length(); i++) {
105       if (basePath.charAt(i) == separator) {
106         relativePath.append("..");
107         relativePath.append(separator);
108       }
109     }
110     relativePath.append(filePath.substring(lastSeparatorIndex + 1));
111
112     return relativePath.toString();
113   }
114
115   public static boolean isAbsolute(@NotNull String path) {
116     return new File(path).isAbsolute();
117   }
118
119   /**
120    * Check if the {@code ancestor} is an ancestor of {@code file}.
121    *
122    * @param ancestor the file
123    * @param file     the file
124    * @param strict   if {@code false} then this method returns {@code true} if {@code ancestor}
125    *                 and {@code file} are equal
126    * @return {@code true} if {@code ancestor} is parent of {@code file}; {@code false} otherwise
127    */
128   public static boolean isAncestor(@NotNull File ancestor, @NotNull File file, boolean strict) {
129     File parent = strict ? getParentFile(file) : file;
130     while (true) {
131       if (parent == null) {
132         return false;
133       }
134       // Do not user file.equals as it incorrectly works on MacOS
135       if (pathsEqual(parent.getPath(), ancestor.getPath())) {
136         return true;
137       }
138       parent = getParentFile(parent);
139     }
140   }
141
142   /**
143    * Get parent for the file. The method correctly
144    * processes "." and ".." in file names. The name
145    * remains relative if was relative before.
146    *
147    * @param file a file to analyze
148    * @return a parent or the null if the file has no parent.
149    */
150   @Nullable
151   public static File getParentFile(@NotNull File file) {
152     int skipCount = 0;
153     File parentFile = file;
154     while (true) {
155       parentFile = parentFile.getParentFile();
156       if (parentFile == null) {
157         return null;
158       }
159       if (".".equals(parentFile.getName())) {
160         continue;
161       }
162       if ("..".equals(parentFile.getName())) {
163         skipCount++;
164         continue;
165       }
166       if (skipCount > 0) {
167         skipCount--;
168         continue;
169       }
170       return parentFile;
171     }
172   }
173
174   @NotNull
175   public static String loadFile(@NotNull File file) throws IOException {
176     return loadFile(file, null);
177   }
178
179   @NotNull
180   public static String loadFile(@NotNull File file, String encoding) throws IOException {
181     return new String(loadFileText(file, encoding));
182   }
183
184   @NotNull
185   public static char[] loadFileText(@NotNull File file) throws IOException {
186     return loadFileText(file, null);
187   }
188
189   @NotNull
190   public static char[] loadFileText(@NotNull File file, @NonNls String encoding) throws IOException {
191     InputStream stream = new FileInputStream(file);
192     Reader reader = encoding == null ? new InputStreamReader(stream) : new InputStreamReader(stream, encoding);
193     try {
194       return loadText(reader, (int)file.length());
195     }
196     finally {
197       reader.close();
198     }
199   }
200
201   @NotNull
202   public static char[] loadText(@NotNull Reader reader, int length) throws IOException {
203     char[] chars = new char[length];
204     int count = 0;
205     while (count < chars.length) {
206       int n = reader.read(chars, count, chars.length - count);
207       if (n <= 0) break;
208       count += n;
209     }
210     if (count == chars.length) {
211       return chars;
212     }
213     else {
214       char[] newChars = new char[count];
215       System.arraycopy(chars, 0, newChars, 0, count);
216       return newChars;
217     }
218   }
219
220   @NotNull
221   public static byte[] loadFileBytes(@NotNull File file) throws IOException {
222     byte[] bytes;
223     final InputStream stream = new FileInputStream(file);
224     try {
225       final long len = file.length();
226       if (len < 0) {
227         throw new IOException("File length reported negative, probably doesn't exist");
228       }
229
230       if (len > 100 * MEGABYTE) {
231         throw new FileTooBigException("Attempt to load '" + file + "' in memory buffer, file length is " + len + " bytes.");
232       }
233
234       bytes = loadBytes(stream, (int)len);
235     }
236     finally {
237       stream.close();
238     }
239     return bytes;
240   }
241
242   @NotNull
243   public static byte[] loadBytes(@NotNull InputStream stream, int length) throws IOException {
244     byte[] bytes = new byte[length];
245     int count = 0;
246     while (count < length) {
247       int n = stream.read(bytes, count, length - count);
248       if (n <= 0) break;
249       count += n;
250     }
251     return bytes;
252   }
253
254   @NotNull
255   public static byte[] loadBytes(@NotNull InputStream stream) throws IOException {
256     ByteArrayOutputStream buffer = new ByteArrayOutputStream();
257     final byte[] bytes = BUFFER.get();
258     while (true) {
259       int n = stream.read(bytes, 0, bytes.length);
260       if (n <= 0) break;
261       buffer.write(bytes, 0, n);
262     }
263     buffer.close();
264     return buffer.toByteArray();
265   }
266
267   public static boolean processFirstBytes(@NotNull InputStream stream, int length, @NotNull Processor<ByteSequence> processor) throws IOException {
268     final byte[] bytes = BUFFER.get();
269     assert bytes.length >= length : "Cannot process more than " + bytes.length + " in one call, requested:" + length;
270
271     int n = stream.read(bytes, 0, length);
272     if (n <= 0) return false;
273
274     return processor.process(new ByteSequence(bytes, 0, n));
275   }
276
277   @NotNull
278   public static byte[] loadFirst(@NotNull InputStream stream, int maxLength) throws IOException {
279     ByteArrayOutputStream buffer = new ByteArrayOutputStream();
280     final byte[] bytes = BUFFER.get();
281     while (maxLength > 0) {
282       int n = stream.read(bytes, 0, Math.min(maxLength, bytes.length));
283       if (n <= 0) break;
284       buffer.write(bytes, 0, n);
285       maxLength -= n;
286     }
287     buffer.close();
288     return buffer.toByteArray();
289   }
290
291   @NotNull
292   public static String loadTextAndClose(@NotNull InputStream stream) throws IOException {
293     //noinspection IOResourceOpenedButNotSafelyClosed
294     return loadTextAndClose(new InputStreamReader(stream));
295   }
296
297   @NotNull
298   public static String loadTextAndClose(@NotNull Reader reader) throws IOException {
299     try {
300       return new String(adaptiveLoadText(reader));
301     }
302     finally {
303       reader.close();
304     }
305   }
306
307   @NotNull
308   public static char[] adaptiveLoadText(@NotNull Reader reader) throws IOException {
309     char[] chars = new char[4096];
310     List<char[]> buffers = null;
311     int count = 0;
312     int total = 0;
313     while (true) {
314       int n = reader.read(chars, count, chars.length - count);
315       if (n <= 0) break;
316       count += n;
317       if (total > 1024 * 1024 * 10) throw new FileTooBigException("File too big " + reader);
318       total += n;
319       if (count == chars.length) {
320         if (buffers == null) {
321           buffers = new ArrayList<char[]>();
322         }
323         buffers.add(chars);
324         int newLength = Math.min(1024 * 1024, chars.length * 2);
325         chars = new char[newLength];
326         count = 0;
327       }
328     }
329     char[] result = new char[total];
330     if (buffers != null) {
331       for (char[] buffer : buffers) {
332         System.arraycopy(buffer, 0, result, result.length - total, buffer.length);
333         total -= buffer.length;
334       }
335     }
336     System.arraycopy(chars, 0, result, result.length - total, total);
337     return result;
338   }
339
340   @NotNull
341   public static byte[] adaptiveLoadBytes(@NotNull InputStream stream) throws IOException {
342     byte[] bytes = new byte[4096];
343     List<byte[]> buffers = null;
344     int count = 0;
345     int total = 0;
346     while (true) {
347       int n = stream.read(bytes, count, bytes.length - count);
348       if (n <= 0) break;
349       count += n;
350       if (total > 1024 * 1024 * 10) throw new FileTooBigException("File too big " + stream);
351       total += n;
352       if (count == bytes.length) {
353         if (buffers == null) {
354           buffers = new ArrayList<byte[]>();
355         }
356         buffers.add(bytes);
357         int newLength = Math.min(1024 * 1024, bytes.length * 2);
358         bytes = new byte[newLength];
359         count = 0;
360       }
361     }
362     byte[] result = new byte[total];
363     if (buffers != null) {
364       for (byte[] buffer : buffers) {
365         System.arraycopy(buffer, 0, result, result.length - total, buffer.length);
366         total -= buffer.length;
367       }
368     }
369     System.arraycopy(bytes, 0, result, result.length - total, total);
370     return result;
371   }
372
373   @NotNull
374   public static File createTempDirectory(@NotNull @NonNls String prefix, @Nullable @NonNls String suffix) throws IOException {
375     File file = doCreateTempFile(prefix, suffix);
376     file.delete();
377     file.mkdir();
378     file.deleteOnExit();
379     return file;
380   }
381
382   @NotNull
383   public static File createTempDirectory(File dir, @NotNull @NonNls String prefix, @Nullable @NonNls String suffix) throws IOException {
384     File file = doCreateTempFile(prefix, suffix, dir);
385     file.delete();
386     file.mkdir();
387     file.deleteOnExit();
388     return file;
389   }
390
391   @NotNull
392   public static File createTempFile(@NonNls final File dir, @NotNull @NonNls String prefix, @Nullable @NonNls String suffix, final boolean create)
393     throws IOException {
394     return createTempFile(dir, prefix, suffix, create, true);
395   }
396
397   public static File createTempFile(@NonNls final File dir,
398                                     @NotNull @NonNls String prefix,
399                                     @Nullable @NonNls String suffix,
400                                     final boolean create,
401                                     boolean deleteOnExit) throws IOException {
402     File file = doCreateTempFile(prefix, suffix, dir);
403     file.delete();
404     if (create) {
405       file.createNewFile();
406     }
407     if (deleteOnExit) {
408       file.deleteOnExit();
409     }
410     return file;
411   }
412
413   @NotNull
414   public static File createTempFile(@NotNull @NonNls String prefix, @Nullable @NonNls String suffix) throws IOException {
415     return createTempFile(prefix, suffix, false); //false until TeamCity fixes its plugin
416   }
417
418   @NotNull
419   public static File createTempFile(@NotNull @NonNls String prefix, @Nullable @NonNls String suffix, boolean deleteOnExit) throws IOException {
420     File file = doCreateTempFile(prefix, suffix);
421     file.delete();
422     file.createNewFile();
423     if (deleteOnExit) {
424       file.deleteOnExit();
425     }
426     return file;
427   }
428
429   @NotNull
430   private static File doCreateTempFile(String prefix, String suffix) throws IOException {
431     return doCreateTempFile(prefix, suffix, new File(getTempDirectory()));
432   }
433
434   @NotNull
435   private static File doCreateTempFile(@NotNull String prefix, String suffix, final File dir) throws IOException {
436     dir.mkdirs();
437
438     if (prefix.length() < 3) {
439       prefix = (prefix + "___").substring(0, 3);
440     }
441
442     int exceptionsCount = 0;
443     while (true) {
444       try {
445         //noinspection SSBasedInspection
446         final File temp = File.createTempFile(prefix, suffix, dir);
447         return normalizeFile(temp);
448       }
449       catch (IOException e) { // Win32 createFileExclusively access denied
450         if (++exceptionsCount >= 100) {
451           throw e;
452         }
453       }
454     }
455   }
456
457   private static File normalizeFile(File temp) throws IOException {
458     final File canonical = temp.getCanonicalFile();
459     return SystemInfo.isWindows && canonical.getAbsolutePath().contains(" ") ? temp.getAbsoluteFile() : canonical;
460   }
461
462   public static String getTempDirectory() {
463     if (ourCanonicalTempPathCache == null) {
464       ourCanonicalTempPathCache = calcCanonicalTempPath();
465     }
466     return ourCanonicalTempPathCache;
467   }
468
469   @TestOnly
470   public static void resetCanonicalTempPathCache(final String tempPath) {
471     ourCanonicalTempPathCache = tempPath;
472   }
473
474   private static String calcCanonicalTempPath() {
475     final File file = new File(System.getProperty("java.io.tmpdir"));
476     try {
477       final String canonical = file.getCanonicalPath();
478       if (!SystemInfo.isWindows || !canonical.contains(" ")) {
479         return canonical;
480       }
481     }
482     catch (IOException ignore) {
483     }
484     return file.getAbsolutePath();
485   }
486
487   public static void asyncDelete(@NotNull File file) {
488     final File tempFile = renameToTempFileOrDelete(file);
489     if (tempFile == null) {
490       return;
491     }
492     startDeletionThread(tempFile);
493   }
494
495   public static void asyncDelete(@NotNull Collection<File> files) {
496     List<File> tempFiles = new ArrayList<File>();
497     for (File file : files) {
498       final File tempFile = renameToTempFileOrDelete(file);
499       if (tempFile != null) {
500         tempFiles.add(tempFile);
501       }
502     }
503     if (!tempFiles.isEmpty()) {
504       startDeletionThread(tempFiles.toArray(new File[tempFiles.size()]));
505     }
506   }
507
508   private static void startDeletionThread(@NotNull final File... tempFiles) {
509     final Runnable deleteFilesTask = new Runnable() {
510       public void run() {
511         final Thread currentThread = Thread.currentThread();
512         currentThread.setPriority(Thread.MIN_PRIORITY);
513         //ShutDownTracker.getInstance().registerStopperThread(currentThread);
514         try {
515           for (File tempFile : tempFiles) {
516             delete(tempFile);
517           }
518         }
519         finally {
520           //ShutDownTracker.getInstance().unregisterStopperThread(currentThread);
521           currentThread.setPriority(Thread.NORM_PRIORITY);
522         }
523       }
524     };
525
526     try {
527 // Attempt to execute on pooled thread
528       final Class<?> aClass = Class.forName("com.intellij.openapi.application.ApplicationManager");
529       final Method getApplicationMethod = aClass.getMethod("getApplication");
530       final Object application = getApplicationMethod.invoke(null);
531       final Method executeOnPooledThreadMethod = application.getClass().getMethod("executeOnPooledThread", Runnable.class);
532       executeOnPooledThreadMethod.invoke(application, deleteFilesTask);
533     }
534     catch (Exception e) {
535       //noinspection HardCodedStringLiteral
536       Thread t = new Thread(deleteFilesTask, "File deletion thread");
537       t.start();
538     }
539   }
540
541   private static File renameToTempFileOrDelete(@NotNull File file) {
542     final File tempDir = new File(getTempDirectory());
543     boolean isSameDrive = true;
544     if (SystemInfo.isWindows) {
545       String tempDirDrive = tempDir.getAbsolutePath().substring(0, 2);
546       String fileDrive = file.getAbsolutePath().substring(0, 2);
547       isSameDrive = tempDirDrive.equalsIgnoreCase(fileDrive);
548     }
549
550     if (isSameDrive) {
551       // the optimization is reasonable only if destination dir is located on the same drive
552       final String originalFileName = file.getName();
553       File tempFile = getTempFile(originalFileName, tempDir);
554       if (file.renameTo(tempFile)) {
555         return tempFile;
556       }
557     }
558
559     delete(file);
560
561     return null;
562   }
563
564   private static File getTempFile(@NotNull String originalFileName, @NotNull File parent) {
565     int randomSuffix = (int)(System.currentTimeMillis() % 1000);
566     for (int i = randomSuffix; ; i++) {
567       @NonNls String name = "___" + originalFileName + i + ASYNC_DELETE_EXTENSION;
568       File tempFile = new File(parent, name);
569       if (!tempFile.exists()) return tempFile;
570     }
571   }
572
573   public static boolean delete(@NotNull File file) {
574     if (file.isDirectory() && !FileSystemUtil.isSymLink(file)) {
575       File[] files = file.listFiles();
576       if (files != null) {
577         for (File child : files) {
578           if (!delete(child)) return false;
579         }
580       }
581     }
582
583     for (int i = 0; i < MAX_FILE_DELETE_ATTEMPTS; i++) {
584       if (file.delete() || !file.exists()) return true;
585       try {
586         //noinspection BusyWait
587         Thread.sleep(10);
588       }
589       catch (InterruptedException ignored) { }
590     }
591     return false;
592   }
593
594   public static boolean createParentDirs(@NotNull File file) {
595     if (!file.exists()) {
596       String parentDirPath = file.getParent();
597       if (parentDirPath != null) {
598         final File parentFile = new File(parentDirPath);
599         return parentFile.exists() && parentFile.isDirectory() || parentFile.mkdirs();
600       }
601     }
602     return true;
603   }
604
605   public static boolean createIfDoesntExist(@NotNull File file) {
606     if (file.exists()) return true;
607     try {
608       if (!createParentDirs(file)) return false;
609
610       OutputStream s = new FileOutputStream(file);
611       s.close();
612       return true;
613     }
614     catch (IOException e) {
615       LOG.info(e);
616       return false;
617     }
618   }
619
620   public static boolean ensureCanCreateFile(@NotNull File file) {
621     if (file.exists()) return file.canWrite();
622     if (!createIfDoesntExist(file)) return false;
623     return delete(file);
624   }
625
626   public static void copy(@NotNull File fromFile, @NotNull File toFile) throws IOException {
627     performCopy(fromFile, toFile, true);
628   }
629
630   public static void copyContent(@NotNull File fromFile, @NotNull File toFile) throws IOException {
631     performCopy(fromFile, toFile, false);
632   }
633
634   @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
635   private static void performCopy(@NotNull File fromFile, @NotNull File toFile, final boolean syncTimestamp) throws IOException {
636     FileOutputStream fos;
637     try {
638       fos = new FileOutputStream(toFile);
639     }
640     catch (FileNotFoundException e) {
641       File parentFile = toFile.getParentFile();
642       if (parentFile == null) {
643         final IOException ioException = new IOException("parent file is null for " + toFile.getPath());
644         ioException.initCause(e);
645         throw ioException;
646       }
647       createParentDirs(toFile);
648       fos = new FileOutputStream(toFile);
649     }
650
651     if (Patches.FILE_CHANNEL_TRANSFER_BROKEN || fromFile.length() > CHANNELS_COPYING_LIMIT) {
652       FileInputStream fis = new FileInputStream(fromFile);
653       try {
654         copy(fis, fos);
655       }
656       finally {
657         fis.close();
658         fos.close();
659       }
660     }
661     else {
662       FileChannel fromChannel = new FileInputStream(fromFile).getChannel();
663       FileChannel toChannel = fos.getChannel();
664       try {
665         fromChannel.transferTo(0, Long.MAX_VALUE, toChannel);
666       }
667       finally {
668         fromChannel.close();
669         toChannel.close();
670       }
671     }
672
673     if (syncTimestamp) {
674       final long timeStamp = fromFile.lastModified();
675       if (!toFile.setLastModified(timeStamp)) {
676         LOG.warn("Unable to set timestamp " + timeStamp + " to " + toFile);
677       }
678     }
679
680     if (SystemInfo.isUnix && fromFile.canExecute()) {
681       final int oldPermissions = FileSystemUtil.getPermissions(fromFile);
682       final int newPermissions = FileSystemUtil.getPermissions(toFile);
683       if (oldPermissions != -1 && newPermissions != -1) {
684         FileSystemUtil.setPermissions(toFile, (oldPermissions | newPermissions));
685       }
686     }
687   }
688
689   public static void copy(@NotNull InputStream inputStream, @NotNull OutputStream outputStream) throws IOException {
690     final byte[] buffer = BUFFER.get();
691     while (true) {
692       int read = inputStream.read(buffer);
693       if (read < 0) break;
694       outputStream.write(buffer, 0, read);
695     }
696   }
697
698   public static void copy(@NotNull InputStream inputStream, int maxSize, @NotNull OutputStream outputStream) throws IOException {
699     final byte[] buffer = BUFFER.get();
700     int toRead = maxSize;
701     while (toRead > 0) {
702       int read = inputStream.read(buffer, 0, Math.min(buffer.length, toRead));
703       if (read < 0) break;
704       toRead -= read;
705       outputStream.write(buffer, 0, read);
706     }
707   }
708
709   public static void copyDir(@NotNull File fromDir, @NotNull File toDir) throws IOException {
710     copyDir(fromDir, toDir, true);
711   }
712
713   public static void copyDir(@NotNull File fromDir, @NotNull File toDir, boolean copySystemFiles) throws IOException {
714     copyDir(fromDir, toDir, copySystemFiles ? null : new FileFilter() {
715       public boolean accept(File file) {
716         return !file.getName().startsWith(".");
717       }
718     });
719   }
720
721   public static void copyDir(@NotNull File fromDir, @NotNull File toDir, @Nullable final FileFilter filter) throws IOException {
722     if (!toDir.exists() && !toDir.mkdirs()) {
723       throw new IOException(CommonBundle.message("exception.directory.can.not.create", toDir.getPath()));
724     }
725     if (isAncestor(fromDir, toDir, true)) {
726       LOG.error(fromDir.getAbsolutePath() + " is ancestor of " + toDir + ". Can't copy to itself.");
727       return;
728     }
729     File[] files = fromDir.listFiles();
730     if (files == null) throw new IOException(CommonBundle.message("exception.directory.is.invalid", fromDir.getPath()));
731     if (!fromDir.canRead()) throw new IOException(CommonBundle.message("exception.directory.is.not.readable", fromDir.getPath()));
732     for (File file : files) {
733       if (filter != null && !filter.accept(file)) {
734         continue;
735       }
736       if (file.isDirectory()) {
737         copyDir(file, new File(toDir, file.getName()), filter);
738       }
739       else {
740         copy(file, new File(toDir, file.getName()));
741       }
742     }
743   }
744
745   @NotNull
746   public static String getNameWithoutExtension(@NotNull File file) {
747     return getNameWithoutExtension(file.getName());
748   }
749
750   @NotNull
751   public static String getNameWithoutExtension(@NotNull String name) {
752     int i = name.lastIndexOf('.');
753     if (i != -1) {
754       name = name.substring(0, i);
755     }
756     return name;
757   }
758
759   public static String createSequentFileName(@NotNull File aParentFolder, @NotNull @NonNls String aFilePrefix, @NotNull String aExtension) {
760     return findSequentNonexistentFile(aParentFolder, aFilePrefix, aExtension).getName();
761   }
762
763   public static File findSequentNonexistentFile(@NotNull File aParentFolder,
764                                                 @NotNull @NonNls final String aFilePrefix,
765                                                 @NotNull String aExtension) {
766     int postfix = 0;
767     String ext = aExtension.isEmpty() ? "" : "." + aExtension;
768
769     File candidate = new File(aParentFolder, aFilePrefix + ext);
770     while (candidate.exists()) {
771       postfix++;
772       candidate = new File(aParentFolder, aFilePrefix + Integer.toString(postfix) + ext);
773     }
774     return candidate;
775   }
776
777   @NotNull
778   public static String toSystemDependentName(@NonNls @NotNull String aFileName) {
779     return aFileName.replace('/', File.separatorChar).replace('\\', File.separatorChar);
780   }
781
782   @NotNull
783   public static String toSystemIndependentName(@NonNls @NotNull String aFileName) {
784     return aFileName.replace('\\', '/');
785   }
786
787   @NotNull
788   public static String nameToCompare(@NonNls @NotNull String name) {
789     return (SystemInfo.isFileSystemCaseSensitive ? name : name.toLowerCase()).replace('\\', '/');
790   }
791
792   public static String toCanonicalPath(String path) {
793     if (path == null || path.isEmpty()) {
794       return path;
795     }
796     path = path.replace(File.separatorChar, '/');
797     final StringTokenizer tok = new StringTokenizer(path, "/");
798     final Stack<String> stack = new Stack<String>();
799     while (tok.hasMoreTokens()) {
800       final String token = tok.nextToken();
801       if ("..".equals(token)) {
802         if (stack.isEmpty()) {
803           return null;
804         }
805         stack.pop();
806       }
807       else if (!token.isEmpty() && !".".equals(token)) {
808         stack.push(token);
809       }
810     }
811     final StringBuilder result = new StringBuilder(path.length());
812     if (path.charAt(0) == '/') {
813       result.append("/");
814     }
815     for (int i = 0; i < stack.size(); i++) {
816       String str = stack.get(i);
817       if (i > 0) {
818         result.append('/');
819       }
820       result.append(str);
821     }
822     return result.toString();
823   }
824
825   @NotNull
826   public static String unquote(@NotNull String urlString) {
827     urlString = urlString.replace('/', File.separatorChar);
828     return URLUtil.unescapePercentSequences(urlString);
829   }
830
831   public static boolean isFilePathAcceptable(@NotNull File root, @Nullable FileFilter fileFilter) {
832     File file = root;
833     do {
834       if (fileFilter != null && !fileFilter.accept(file)) return false;
835       file = file.getParentFile();
836     }
837     while (file != null);
838     return true;
839   }
840
841   public static void rename(@NotNull File source, @NotNull File target) throws IOException {
842     if (source.renameTo(target)) return;
843     if (!source.exists()) return;
844
845     copy(source, target);
846     delete(source);
847   }
848
849   public static boolean startsWith(@NotNull @NonNls String path, @NotNull @NonNls String start) {
850     return startsWith(path, start, SystemInfo.isFileSystemCaseSensitive);
851   }
852
853   public static boolean startsWith(@NotNull String path, @NotNull String start, final boolean caseSensitive) {
854     final int length1 = path.length();
855     final int length2 = start.length();
856     if (length2 == 0) return true;
857     if (length2 > length1) return false;
858     if (!path.regionMatches(!caseSensitive, 0, start, 0, length2)) return false;
859     if (length1 == length2) return true;
860     char last2 = start.charAt(length2 - 1);
861     char next1;
862     if (last2 == '/' || last2 == File.separatorChar) {
863       next1 = path.charAt(length2 - 1);
864     }
865     else {
866       next1 = path.charAt(length2);
867     }
868     return next1 == '/' || next1 == File.separatorChar;
869   }
870
871   public static boolean pathsEqual(@NotNull String path1, @NotNull String path2) {
872     return SystemInfo.isFileSystemCaseSensitive ? path1.equals(path2) : path1.equalsIgnoreCase(path2);
873   }
874
875   public static int comparePaths(@NotNull String path1, @NotNull String path2) {
876     return SystemInfo.isFileSystemCaseSensitive ? path1.compareTo(path2) : path1.compareToIgnoreCase(path2);
877   }
878
879   public static int pathHashCode(@NotNull String path) {
880     return SystemInfo.isFileSystemCaseSensitive ? path.hashCode() : path.toLowerCase().hashCode();
881   }
882
883   @NotNull
884   public static String getExtension(@NotNull String fileName) {
885     int index = fileName.lastIndexOf('.');
886     if (index < 0) return "";
887     return fileName.substring(index + 1).toLowerCase();
888   }
889
890   @NotNull
891   public static String resolveShortWindowsName(@NotNull final String path) throws IOException {
892     if (SystemInfo.isWindows) {
893       //todo: this resolves symlinks on Windows, but we'd rather not do it
894       return new File(path.replace(File.separatorChar, '/')).getCanonicalPath();
895     }
896     return path;
897   }
898
899   public static void collectMatchedFiles(@NotNull File root, @NotNull Pattern pattern, @NotNull List<File> outFiles) {
900     collectMatchedFiles(root, root, pattern, outFiles);
901   }
902
903   private static void collectMatchedFiles(@NotNull File absoluteRoot,
904                                           @NotNull File root,
905                                           @NotNull Pattern pattern,
906                                           @NotNull List<File> files) {
907     final File[] dirs = root.listFiles();
908     if (dirs == null) return;
909     for (File dir : dirs) {
910       if (dir.isFile()) {
911         final String path = toSystemIndependentName(getRelativePath(absoluteRoot, dir));
912         if (pattern.matcher(path).matches()) {
913           files.add(dir);
914         }
915       }
916       else {
917         collectMatchedFiles(absoluteRoot, dir, pattern, files);
918       }
919     }
920   }
921
922   @RegExp
923   @NotNull
924   public static String convertAntToRegexp(@NotNull String antPattern) {
925     return convertAntToRegexp(antPattern, true);
926   }
927
928   /**
929    * @param antPattern ant-style path pattern
930    * @return java regexp pattern.
931    *         Note that no matter whether forward or backward slashes were used in the antPattern
932    *         the returned regexp pattern will use forward slashes ('/') as file separators.
933    *         Paths containing windows-style backslashes must be converted before matching against the resulting regexp
934    * @see com.intellij.openapi.util.io.FileUtil#toSystemIndependentName
935    */
936   @RegExp
937   @NotNull
938   public static String convertAntToRegexp(@NotNull String antPattern, boolean ignoreStartingSlash) {
939     final StringBuilder builder = new StringBuilder(antPattern.length());
940     int asteriskCount = 0;
941     boolean recursive = true;
942     final int start = ignoreStartingSlash && (antPattern.startsWith("/") || antPattern.startsWith("\\")) ? 1 : 0;
943     for (int idx = start; idx < antPattern.length(); idx++) {
944       final char ch = antPattern.charAt(idx);
945
946       if (ch == '*') {
947         asteriskCount++;
948         continue;
949       }
950
951       final boolean foundRecursivePattern = recursive && asteriskCount == 2 && (ch == '/' || ch == '\\');
952       final boolean asterisksFound = asteriskCount > 0;
953
954       asteriskCount = 0;
955       recursive = ch == '/' || ch == '\\';
956
957       if (foundRecursivePattern) {
958         builder.append("(?:[^/]+/)*?");
959         continue;
960       }
961
962       if (asterisksFound) {
963         builder.append("[^/]*?");
964       }
965
966       if (ch == '(' ||
967           ch == ')' ||
968           ch == '[' ||
969           ch == ']' ||
970           ch == '^' ||
971           ch == '$' ||
972           ch == '.' ||
973           ch == '{' ||
974           ch == '}' ||
975           ch == '+' ||
976           ch == '|') {
977         // quote regexp-specific symbols
978         builder.append('\\').append(ch);
979         continue;
980       }
981       if (ch == '?') {
982         builder.append("[^/]{1}");
983         continue;
984       }
985       if (ch == '\\') {
986         builder.append('/');
987         continue;
988       }
989       builder.append(ch);
990     }
991
992     // handle ant shorthand: mypackage/test/ is interpreted as if it were mypackage/test/**
993     final boolean isTrailingSlash = builder.length() > 0 && builder.charAt(builder.length() - 1) == '/';
994     if (asteriskCount == 0 && isTrailingSlash || recursive && asteriskCount == 2) {
995       if (isTrailingSlash) {
996         builder.setLength(builder.length() - 1);
997       }
998       if (builder.length() == 0) {
999         builder.append(".*");
1000       }
1001       else {
1002         builder.append("(?:$|/.+)");
1003       }
1004     }
1005     else if (asteriskCount > 0) {
1006       builder.append("[^/]*?");
1007     }
1008     return builder.toString();
1009   }
1010
1011   public static boolean moveDirWithContent(@NotNull File fromDir, @NotNull File toDir) {
1012     if (!toDir.exists()) return fromDir.renameTo(toDir);
1013
1014     File[] files = fromDir.listFiles();
1015     if (files == null) return false;
1016
1017     boolean success = true;
1018
1019     for (File fromFile : files) {
1020       File toFile = new File(toDir, fromFile.getName());
1021       success = success && fromFile.renameTo(toFile);
1022     }
1023     fromDir.delete();
1024
1025     return success;
1026   }
1027
1028   /**
1029    * Has duplicate: {@link com.intellij.coverage.listeners.CoverageListener#sanitize(java.lang.String, java.lang.String)}
1030    * as FileUtil is not available in client's vm
1031    */
1032   @NotNull
1033   public static String sanitizeFileName(@NotNull String name) {
1034     StringBuilder result = new StringBuilder();
1035
1036     for (int i = 0; i < name.length(); i++) {
1037       final char ch = name.charAt(i);
1038
1039       if (ch > 0 && ch < 255) {
1040         if (Character.isLetterOrDigit(ch)) {
1041           result.append(ch);
1042         }
1043         else {
1044           result.append("_");
1045         }
1046       }
1047     }
1048
1049     return result.toString();
1050   }
1051
1052   public static boolean canExecute(@NotNull File file) {
1053     return file.canExecute();
1054   }
1055
1056   /** @deprecated use {@link FileSystemUtil#isSymLink(File)} (to remove in IDEA 12) */
1057   @SuppressWarnings("UnusedDeclaration")
1058   public static boolean isSymbolicLink(File file) {
1059     return FileSystemUtil.isSymLink(file);
1060   }
1061
1062   /** @deprecated use {@link FileSystemUtil#isSymLink(File)} (to remove in IDEA 12) */
1063   @SuppressWarnings("UnusedDeclaration")
1064   public static boolean isSymbolicLink(File parent, String name) {
1065     return FileSystemUtil.isSymLink(new File(parent, name));
1066   }
1067
1068   public static void setReadOnlyAttribute(@NotNull String path, boolean readOnlyFlag) throws IOException {
1069     final boolean writableFlag = !readOnlyFlag;
1070     final File file = new File(path);
1071     if (!file.setWritable(writableFlag) && file.canWrite() != writableFlag) {
1072       LOG.warn("Can't set writable attribute of '" + path + "' to " + readOnlyFlag);
1073     }
1074   }
1075
1076   /**
1077    * Set executable attribute, it makes sense only on non-windows platforms.
1078    *
1079    * @param path           the path to use
1080    * @param executableFlag new value of executable attribute
1081    * @throws IOException if there is a problem with setting the flag
1082    */
1083   public static void setExecutableAttribute(@NotNull String path, boolean executableFlag) throws IOException {
1084     final File file = new File(path);
1085     if (!file.setExecutable(executableFlag) && file.canExecute() != executableFlag) {
1086       LOG.warn("Can't set executable attribute of '" + path + "' to " + executableFlag);
1087     }
1088   }
1089
1090   public static void appendToFile(@NotNull File file, @NotNull String text) throws IOException {
1091     writeToFile(file, text.getBytes("UTF-8"), true);
1092   }
1093
1094   public static void writeToFile(@NotNull File file, @NotNull byte[] text) throws IOException {
1095     writeToFile(file, text, false);
1096   }
1097
1098   public static void writeToFile(@NotNull File file, @NotNull String text) throws IOException {
1099     writeToFile(file, text.getBytes("UTF-8"), false);
1100   }
1101
1102   public static void writeToFile(@NotNull File file, @NotNull byte[] text, int off, int len) throws IOException {
1103     writeToFile(file, text, off, len, false);
1104   }
1105
1106   public static void writeToFile(@NotNull File file, @NotNull byte[] text, boolean append) throws IOException {
1107     writeToFile(file, text, 0, text.length, append);
1108   }
1109
1110   private static void writeToFile(@NotNull File file, @NotNull byte[] text, final int off, final int len, boolean append)
1111     throws IOException {
1112     createParentDirs(file);
1113     OutputStream stream = new BufferedOutputStream(new FileOutputStream(file, append));
1114     try {
1115       stream.write(text, off, len);
1116     }
1117     finally {
1118       stream.close();
1119     }
1120   }
1121
1122   public static boolean processFilesRecursively(@NotNull File root, @NotNull Processor<File> processor) {
1123     final LinkedList<File> queue = new LinkedList<File>();
1124     queue.add(root);
1125     while (!queue.isEmpty()) {
1126       final File file = queue.removeFirst();
1127       if (!processor.process(file)) return false;
1128       if (file.isDirectory()) {
1129         final File[] children = file.listFiles();
1130         if (children != null) {
1131           ContainerUtil.addAll(queue, children);
1132         }
1133       }
1134     }
1135     return true;
1136   }
1137
1138   @Nullable
1139   public static File findFirstThatExist(@NotNull String... paths) {
1140     for (String path : paths) {
1141       if (!StringUtil.isEmptyOrSpaces(path)) {
1142         File file = new File(toSystemDependentName(path));
1143         if (file.exists()) return file;
1144       }
1145     }
1146
1147     return null;
1148   }
1149
1150   @NotNull
1151   public static List<File> findFilesByMask(@NotNull Pattern pattern, @NotNull File dir) {
1152     final ArrayList<File> found = new ArrayList<File>();
1153     for (File file : dir.listFiles()) {
1154       if (file.isDirectory()) {
1155         found.addAll(findFilesByMask(pattern, file));
1156       }
1157       else {
1158         if (pattern.matcher(file.getName()).matches()) {
1159           found.add(file);
1160         }
1161       }
1162     }
1163     return found;
1164   }
1165
1166   @NotNull
1167   public static List<File> findFilesOrDirsByMask(@NotNull Pattern pattern, @NotNull File dir) {
1168     final ArrayList<File> found = new ArrayList<File>();
1169     for (File file : dir.listFiles()) {
1170       if (file.isDirectory()) {
1171         found.addAll(findFilesOrDirsByMask(pattern, file));
1172       }
1173
1174       if (pattern.matcher(file.getName()).matches()) {
1175         found.add(file);
1176       }
1177     }
1178     return found;
1179   }
1180
1181   /**
1182    * Returns empty string for empty path.
1183    * First checks whether provided path is a path of a file with sought-for name.
1184    * Unless found, checks if provided file was a directory. In this case checks existance
1185    * of child files with given names in order "as provided". Finally checks filename among
1186    * brother-files of provided. Returns null if nothing found.
1187    *
1188    * @return path of the first of found files or empty string or null.
1189    */
1190   @Nullable
1191   public static String findFileInProvidedPath(String providedPath, String... fileNames) {
1192     if (StringUtil.isEmpty(providedPath)) {
1193       return "";
1194     }
1195
1196     File providedFile = new File(providedPath);
1197     if (providedFile.exists()) {
1198       String name = providedFile.getName();
1199       for (String fileName : fileNames) {
1200         if (name.equals(fileName)) {
1201           return toSystemDependentName(providedFile.getPath());
1202         }
1203       }
1204     }
1205
1206     if (providedFile.isDirectory()) {  //user chose folder with file
1207       for (String fileName : fileNames) {
1208         File file = new File(providedFile, fileName);
1209         if (fileName.equals(file.getName()) && file.exists()) {
1210           return toSystemDependentName(file.getPath());
1211         }
1212       }
1213     }
1214
1215     providedFile = providedFile.getParentFile();  //users chose wrong file in same directory
1216     if (providedFile != null && providedFile.exists()) {
1217       for (String fileName : fileNames) {
1218         File file = new File(providedFile, fileName);
1219         if (fileName.equals(file.getName()) && file.exists()) {
1220           return toSystemDependentName(file.getPath());
1221         }
1222       }
1223     }
1224
1225     return null;
1226   }
1227
1228   /**
1229    * Treats C: as absolute file path.
1230    */
1231   public static boolean isAbsoluteFilePath(String path) {
1232     return isWindowsAbsolutePath(path) || isAbsolute(path);
1233   }
1234
1235   public static boolean isWindowsAbsolutePath(String pathString) {
1236     if (SystemInfo.isWindows && pathString.length() >= 2 && Character.isLetter(pathString.charAt(0)) && pathString.charAt(1) == ':') {
1237       return true;
1238     }
1239     return false;
1240   }
1241   
1242   @NotNull
1243   public static File generateRandomTemporaryPath() throws IOException {
1244     File file = new File(getTempDirectory(), UUID.randomUUID().toString());
1245     int i = 0;
1246     while (file.exists() && i < 5) {
1247       file = new File(getTempDirectory(), UUID.randomUUID().toString());
1248       ++i;
1249     }
1250     if (file.exists()) {
1251       throw new IOException("Couldn't generate unique random path.");
1252     }
1253     return normalizeFile(file);
1254   }
1255
1256   @Nullable
1257   public static String getLocationRelativeToUserHome(final String path) {
1258     if (path == null) return null;
1259
1260     String _path = path;
1261
1262     if (SystemInfo.isLinux || SystemInfo.isMac) {
1263       final File projectDir = new File(path);
1264       final File userHomeDir = new File(SystemProperties.getUserHome());
1265       if (isAncestor(userHomeDir, projectDir, true)) {
1266         _path = "~/" + getRelativePath(userHomeDir, projectDir);
1267       }
1268     }
1269
1270     return _path;
1271   }
1272
1273   public static boolean isHashBangLine(CharSequence firstCharsIfText, String marker) {
1274     if (firstCharsIfText == null) {
1275       return false;
1276     }
1277     final int lineBreak = StringUtil.indexOf(firstCharsIfText, '\n');
1278     if (lineBreak < 0) {
1279       return false;
1280     }
1281     String firstLine = firstCharsIfText.subSequence(0, lineBreak).toString();
1282     if (!firstLine.startsWith("#!")) {
1283       return false;
1284     }
1285     return firstLine.contains(marker);
1286   }
1287 }