8ed063a63bbf2349a1aac08a57f029f79b4c3a3c
[idea/community.git] / platform / platform-api / src / com / intellij / openapi / vfs / VfsUtil.java
1 /*
2  * Copyright 2000-2009 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.vfs;
17
18 import com.intellij.openapi.application.ApplicationManager;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.fileTypes.FileTypeManager;
21 import com.intellij.openapi.fileTypes.FileTypes;
22 import com.intellij.openapi.util.*;
23 import com.intellij.openapi.util.io.FileUtil;
24 import com.intellij.openapi.util.text.StringUtil;
25 import com.intellij.openapi.vfs.encoding.EncodingManager;
26 import com.intellij.util.ArrayUtil;
27 import com.intellij.util.Function;
28 import com.intellij.util.PathUtil;
29 import com.intellij.util.Processor;
30 import com.intellij.util.containers.Convertor;
31 import com.intellij.util.io.URLUtil;
32 import com.intellij.util.io.fs.FileSystem;
33 import com.intellij.util.io.fs.IFile;
34 import gnu.trove.THashSet;
35 import org.jetbrains.annotations.NonNls;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.Nullable;
38
39 import java.io.*;
40 import java.net.MalformedURLException;
41 import java.net.URL;
42 import java.nio.charset.Charset;
43 import java.util.*;
44
45 public class VfsUtil {
46   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.VfsUtil");
47
48   public static String loadText(@NotNull VirtualFile file) throws IOException{
49     InputStreamReader reader = new InputStreamReader(file.getInputStream(), file.getCharset());
50     try {
51       return new String(FileUtil.loadText(reader, (int)file.getLength()));
52     }
53     finally {
54       reader.close();
55     }
56   }
57
58   public static void saveText(@NotNull VirtualFile file, @NotNull String text) throws IOException {
59     Charset charset = file.getCharset();
60     file.setBinaryContent(text.getBytes(charset.name()));
61   }
62
63   /**
64    * Checks whether the <code>ancestor {@link VirtualFile}</code> is parent of <code>file
65    * {@link VirtualFile}</code>.
66    *
67    * @param ancestor the file
68    * @param file     the file
69    * @param strict   if <code>false</code> then this method returns <code>true</code> if <code>ancestor</code>
70    *                 and <code>file</code> are equal
71    * @return <code>true</code> if <code>ancestor</code> is parent of <code>file</code>; <code>false</code> otherwise
72    */
73   public static boolean isAncestor(@NotNull VirtualFile ancestor, @NotNull VirtualFile file, boolean strict) {
74     if (!file.getFileSystem().equals(ancestor.getFileSystem())) return false;
75     VirtualFile parent = strict ? file.getParent() : file;
76     while (true) {
77       if (parent == null) return false;
78       if (parent.equals(ancestor)) return true;
79       parent = parent.getParent();
80     }
81   }
82
83   /**
84    * Gets the relative path of <code>file</code> to its <code>ancestor</code>. Uses <code>separator</code> for
85    * separating files.
86    *
87    * @param file      the file
88    * @param ancestor  parent file
89    * @param separator character to use as files separator
90    * @return the relative path
91    */
92   public static String getRelativePath(@NotNull VirtualFile file, @NotNull VirtualFile ancestor, char separator) {
93     if (!file.getFileSystem().equals(ancestor.getFileSystem())) return null;
94
95     int length = 0;
96     VirtualFile parent = file;
97     while (true) {
98       if (parent == null) return null;
99       if (parent.equals(ancestor)) break;
100       if (length > 0) {
101         length++;
102       }
103       length += parent.getName().length();
104       parent = parent.getParent();
105     }
106
107     char[] chars = new char[length];
108     int index = chars.length;
109     parent = file;
110     while (true) {
111       if (parent.equals(ancestor)) break;
112       if (index < length) {
113         chars[--index] = separator;
114       }
115       String name = parent.getName();
116       for (int i = name.length() - 1; i >= 0; i--) {
117         chars[--index] = name.charAt(i);
118       }
119       parent = parent.getParent();
120     }
121     return new String(chars);
122   }
123
124   /**
125    * Copies all files matching the <code>filter</code> from <code>fromDir</code> to <code>toDir</code>.
126    *
127    * @param requestor any object to control who called this method. Note that
128    *                  it is considered to be an external change if <code>requestor</code> is <code>null</code>.
129    *                  See {@link VirtualFileEvent#getRequestor}
130    * @param fromDir   the directory to copy from
131    * @param toDir     the directory to copy to
132    * @param filter    {@link VirtualFileFilter}
133    * @throws IOException if files failed to be copied
134    */
135   public static void copyDirectory(Object requestor, @NotNull VirtualFile fromDir, @NotNull VirtualFile toDir, @Nullable VirtualFileFilter filter)
136     throws IOException {
137     VirtualFile[] children = fromDir.getChildren();
138     for (VirtualFile child : children) {
139       if (filter == null || filter.accept(child)) {
140         if (!child.isDirectory()) {
141           copyFile(requestor, child, toDir);
142         }
143         else {
144           VirtualFile newChild = toDir.createChildDirectory(requestor, child.getName());
145           copyDirectory(requestor, child, newChild, filter);
146         }
147       }
148     }
149   }
150
151   /**
152    * Makes a copy of the <code>file</code> in the <code>toDir</code> folder and returns it.
153    *
154    * @param requestor any object to control who called this method. Note that
155    *                  it is considered to be an external change if <code>requestor</code> is <code>null</code>.
156    *                  See {@link VirtualFileEvent#getRequestor}
157    * @param file      file to make a copy of
158    * @param toDir     directory to make a copy in
159    * @return a copy of the file
160    * @throws IOException if file failed to be copied
161    */
162   public static VirtualFile copyFile(Object requestor, @NotNull VirtualFile file, @NotNull VirtualFile toDir) throws IOException {
163     return copyFile(requestor, file, toDir, file.getName());
164   }
165
166   /**
167    * Makes a copy of the <code>file</code> in the <code>toDir</code> folder with the <code>newName</code> and returns it.
168    *
169    * @param requestor any object to control who called this method. Note that
170    *                  it is considered to be an external change if <code>requestor</code> is <code>null</code>.
171    *                  See {@link VirtualFileEvent#getRequestor}
172    * @param file      file to make a copy of
173    * @param toDir     directory to make a copy in
174    * @param newName   new name of the file
175    * @return a copy of the file
176    * @throws IOException if file failed to be copied
177    */
178   public static VirtualFile copyFile(Object requestor, @NotNull VirtualFile file, @NotNull VirtualFile toDir, @NotNull @NonNls String newName)
179     throws IOException {
180     final VirtualFile newChild = toDir.createChildData(requestor, newName);
181     // [jeka] TODO: to be duscussed if the copy should have the same timestamp as the original
182     //OutputStream out = newChild.getOutputStream(requestor, -1, file.getActualTimeStamp());
183     newChild.setBinaryContent(file.contentsToByteArray());
184     return newChild;
185   }
186
187   /**
188    * Copies content of resource to the given file
189    *
190    * @param file to copy to
191    * @param resourceUrl url of the resource to be copied
192    * @throws java.io.IOException if resource not found or copying failed
193    */
194   public static void copyFromResource(@NotNull VirtualFile file, @NonNls @NotNull String resourceUrl) throws IOException {
195     InputStream out = VfsUtil.class.getResourceAsStream(resourceUrl);
196     if (out == null) {
197       throw new FileNotFoundException(resourceUrl);
198     }
199     try {
200       byte[] bytes = FileUtil.adaptiveLoadBytes(out);
201       file.setBinaryContent(bytes);
202     } finally {
203       out.close();
204     }
205   }
206
207   /**
208    * Gets the array of common ancestors for passed files.
209    *
210    * @param files array of files
211    * @return array of common ancestors for passed files
212    */
213   @NotNull
214   public static VirtualFile[] getCommonAncestors(@NotNull VirtualFile[] files) {
215     // Separate files by first component in the path.
216     HashMap<VirtualFile,Set<VirtualFile>> map = new HashMap<VirtualFile, Set<VirtualFile>>();
217     for (VirtualFile aFile : files) {
218       VirtualFile directory = aFile.isDirectory() ? aFile : aFile.getParent();
219       if (directory == null) return VirtualFile.EMPTY_ARRAY;
220       VirtualFile[] path = getPathComponents(directory);
221       Set<VirtualFile> filesSet;
222       final VirtualFile firstPart = path[0];
223       if (map.containsKey(firstPart)) {
224         filesSet = map.get(firstPart);
225       }
226       else {
227         filesSet = new THashSet<VirtualFile>();
228         map.put(firstPart, filesSet);
229       }
230       filesSet.add(directory);
231     }
232     // Find common ancestor for each set of files.
233     ArrayList<VirtualFile> ancestorsList = new ArrayList<VirtualFile>();
234     for (Set<VirtualFile> filesSet : map.values()) {
235       VirtualFile ancestor = null;
236       for (VirtualFile file : filesSet) {
237         if (ancestor == null) {
238           ancestor = file;
239           continue;
240         }
241         ancestor = getCommonAncestor(ancestor, file);
242         //assertTrue(ancestor != null);
243       }
244       ancestorsList.add(ancestor);
245       filesSet.clear();
246     }
247     return VfsUtil.toVirtualFileArray(ancestorsList);
248   }
249
250   /**
251    * Gets the common ancestor for passed files, or null if the files do not have common ancestors.
252    *
253    * @param file1 fist file
254    * @param file2 second file
255    * @return common ancestor for the passed files. Returns <code>null</code> if
256    *         the files do not have common ancestor
257    */
258   public static VirtualFile getCommonAncestor(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
259     if (!file1.getFileSystem().equals(file2.getFileSystem())) {
260       return null;
261     }
262
263     VirtualFile[] path1 = getPathComponents(file1);
264     VirtualFile[] path2 = getPathComponents(file2);
265
266     VirtualFile[] minLengthPath;
267     VirtualFile[] maxLengthPath;
268     if (path1.length < path2.length) {
269       minLengthPath = path1;
270       maxLengthPath = path2;
271     }
272     else {
273       minLengthPath = path2;
274       maxLengthPath = path1;
275     }
276
277     int lastEqualIdx = -1;
278     for (int i = 0; i < minLengthPath.length; i++) {
279       if (minLengthPath[i].equals(maxLengthPath[i])) {
280         lastEqualIdx = i;
281       }
282       else {
283         break;
284       }
285     }
286     return lastEqualIdx == -1 ? null : minLengthPath[lastEqualIdx];
287   }
288
289   /**
290    * Gets an array of files representing paths from root to the passed file.
291    *
292    * @param file the file
293    * @return virtual files which represents paths from root to the passed file
294    */
295   @NotNull
296   private static VirtualFile[] getPathComponents(@NotNull VirtualFile file) {
297     ArrayList<VirtualFile> componentsList = new ArrayList<VirtualFile>();
298     while (file != null) {
299       componentsList.add(file);
300       file = file.getParent();
301     }
302     int size = componentsList.size();
303     VirtualFile[] components = new VirtualFile[size];
304     for (int i = 0; i < size; i++) {
305       components[i] = componentsList.get(size - i - 1);
306     }
307     return components;
308   }
309
310   @Nullable
311   public static VirtualFile findRelativeFile(@NotNull VirtualFile base, String ... path) {
312     VirtualFile file = base;
313
314     for (String pathElement : path) {
315       file = file.findChild(pathElement);
316       if (file == null) return null;
317     }
318
319     return file;
320   }
321
322   @SuppressWarnings({"HardCodedStringLiteral"})
323   @Nullable
324   public static VirtualFile findRelativeFile(@NotNull String uri, VirtualFile base) {
325     if (base != null) {
326       if (!base.isValid()){
327         LOG.error("Invalid file name: " + base.getName() + ", url: " + uri);
328       }
329     }
330
331     uri = uri.replace('\\', '/');
332
333     if (uri.startsWith("file:///")) {
334       uri = uri.substring("file:///".length());
335       if (!SystemInfo.isWindows) uri = "/" + uri;
336     }
337     else if (uri.startsWith("file:/")) {
338       uri = uri.substring("file:/".length());
339       if (!SystemInfo.isWindows) uri = "/" + uri;
340     }
341     else if (uri.startsWith("file:")) {
342       uri = uri.substring("file:".length());
343     }
344
345     VirtualFile file = null;
346
347     if (uri.startsWith("jar:file:/")) {
348       uri = uri.substring("jar:file:/".length());
349       if (!SystemInfo.isWindows) uri = "/" + uri;
350       file = VirtualFileManager.getInstance().findFileByUrl(JarFileSystem.PROTOCOL_PREFIX + uri);
351     }
352     else {
353       if (!SystemInfo.isWindows && StringUtil.startsWithChar(uri, '/')) {
354         file = LocalFileSystem.getInstance().findFileByPath(uri);
355       }
356       else if (SystemInfo.isWindows && uri.length() >= 2 && Character.isLetter(uri.charAt(0)) && uri.charAt(1) == ':') {
357         file = LocalFileSystem.getInstance().findFileByPath(uri);
358       }
359     }
360
361     if (file == null && uri.contains(JarFileSystem.JAR_SEPARATOR)) {
362       file = JarFileSystem.getInstance().findFileByPath(uri);
363       if (file == null && base == null) {
364         file = VirtualFileManager.getInstance().findFileByUrl(uri);
365       }
366     }
367
368     if (file == null) {
369       if (base == null) return LocalFileSystem.getInstance().findFileByPath(uri);
370       if (!base.isDirectory()) base = base.getParent();
371       if (base == null) return LocalFileSystem.getInstance().findFileByPath(uri);
372       file = VirtualFileManager.getInstance().findFileByUrl(base.getUrl() + "/" + uri);
373       if (file == null) return null;
374     }
375
376     return file;
377   }
378
379   @NonNls private static final String FILE = "file";
380   @NonNls private static final String JAR = "jar";
381   @NonNls private static final String MAILTO = "mailto";
382   private static final String PROTOCOL_DELIMITER = ":";
383
384   /**
385    * Searches for the file specified by given java,net.URL.
386    * Note that this method currently tested only for "file" and "jar" protocols under Unix and Windows
387    *
388    * @param url the URL to find file by
389    * @return <code>{@link VirtualFile}</code> if the file was found, <code>null</code> otherwise
390    */
391   public static VirtualFile findFileByURL(@NotNull URL url) {
392     VirtualFileManager virtualFileManager = VirtualFileManager.getInstance();
393     return findFileByURL(url, virtualFileManager);
394   }
395
396   public static VirtualFile findFileByURL(@NotNull URL url, @NotNull VirtualFileManager virtualFileManager) {
397     String vfUrl = convertFromUrl(url);
398     return virtualFileManager.findFileByUrl(vfUrl);
399   }
400
401   /**
402    * Converts VsfUrl info java.net.URL. Does not support "jar:" protocol.
403    *
404    * @param vfsUrl VFS url (as constructed by VfsFile.getUrl())
405    * @return converted URL or null if error has occured
406    */
407
408   @Nullable
409   public static URL convertToURL(@NotNull String vfsUrl) {
410     if (vfsUrl.startsWith(JAR)) {
411       LOG.error("jar: protocol not supported.");
412       return null;
413     }
414
415     // [stathik] for supporting mail URLs in Plugin Manager
416     if (vfsUrl.startsWith(MAILTO)) {
417       try {
418         return new URL (vfsUrl);
419       }
420       catch (MalformedURLException e) {
421         return null;
422       }
423     }
424
425     String[] split = vfsUrl.split("://");
426
427     if (split.length != 2) {
428       LOG.debug("Malformed VFS URL: " + vfsUrl);
429       return null;
430     }
431
432     String protocol = split[0];
433     String path = split[1];
434
435     try {
436       if (protocol.equals(FILE)) {
437         return new URL(protocol, "", path);
438       }
439       else {
440         return new URL(vfsUrl);
441       }
442     }
443     catch (MalformedURLException e) {
444       LOG.debug("MalformedURLException occured:" + e.getMessage());
445       return null;
446     }
447   }
448
449   @NotNull
450   public static String convertFromUrl(@NotNull URL url) {
451     String protocol = url.getProtocol();
452     String path = url.getPath();
453     if (protocol.equals(JAR)) {
454       if (StringUtil.startsWithConcatenationOf(path, FILE, PROTOCOL_DELIMITER)) {
455         try {
456           URL subURL = new URL(path);
457           path = subURL.getPath();
458         }
459         catch (MalformedURLException e) {
460           throw new RuntimeException(VfsBundle.message("url.parse.unhandled.exception"), e);
461         }
462       }
463       else {
464         throw new RuntimeException(new IOException(VfsBundle.message("url.parse.error", url.toExternalForm())));
465       }
466     }
467     if (SystemInfo.isWindows || SystemInfo.isOS2) {
468       while (path.length() > 0 && path.charAt(0) == '/') {
469         path = path.substring(1, path.length());
470       }
471     }
472
473     path = URLUtil.unescapePercentSequences(path);
474     return protocol + "://" + path;
475   }
476
477   public static String urlToPath(@NonNls String url) {
478     if (url == null) return "";
479     return VirtualFileManager.extractPath(url);
480   }
481
482   @NotNull
483   public static String pathToUrl(@NotNull String path) {
484     return VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, path);
485   }
486
487   @NotNull
488   public static File virtualToIoFile(@NotNull VirtualFile file) {
489     return new File(PathUtil.toPresentableUrl(file.getUrl()));
490   }
491
492 @NotNull
493   public static IFile virtualToIFile(@NotNull VirtualFile file) {
494     return FileSystem.FILE_SYSTEM.createFile(PathUtil.toPresentableUrl(file.getUrl()));
495   }
496
497   public static VirtualFile copyFileRelative(Object requestor, @NotNull VirtualFile file, @NotNull VirtualFile toDir, @NotNull String relativePath) throws IOException {
498     StringTokenizer tokenizer = new StringTokenizer(relativePath,"/");
499     VirtualFile curDir = toDir;
500
501     while (true) {
502       String token = tokenizer.nextToken();
503       if (tokenizer.hasMoreTokens()) {
504         VirtualFile childDir = curDir.findChild(token);
505         if (childDir == null) {
506           childDir = curDir.createChildDirectory(requestor, token);
507         }
508         curDir = childDir;
509       }
510       else {
511         return copyFile(requestor, file, curDir, token);
512       }
513     }
514   }
515
516   @NotNull
517   public static String fixIDEAUrl(@NotNull String ideaUrl ) {
518     int idx = ideaUrl.indexOf("://");
519     if( idx >= 0 ) {
520       String s = ideaUrl.substring(0, idx);
521
522       if (s.equals(JarFileSystem.PROTOCOL)) {
523         //noinspection HardCodedStringLiteral
524         s = "jar:file";
525       }
526       ideaUrl = s+":/"+ideaUrl.substring(idx+3);
527     }
528     return ideaUrl;
529   }
530
531   @NotNull
532   public static String fixURLforIDEA(@NotNull String url ) {
533     int idx = url.indexOf(":/");
534     if( idx >= 0 && idx+2 < url.length() && url.charAt(idx+2) != '/' ) {
535       String prefix = url.substring(0, idx);
536       String suffix = url.substring(idx+2);
537
538       if (SystemInfo.isWindows) {
539         url = prefix+"://"+suffix;
540       } else {
541         url = prefix+":///"+suffix;
542       }
543     }
544     return url;
545   }
546
547   public static boolean isAncestor(@NotNull File ancestor, @NotNull File file, boolean strict) {
548     File parent = strict ? file.getParentFile() : file;
549     while (parent != null) {
550       if (parent.equals(ancestor)) return true;
551       parent = parent.getParentFile();
552     }
553
554     return false;
555   }
556
557   /**
558    * Returns the relative path from one virtual file to another.
559    *
560    * @param src           the file from which the relative path is built.
561    * @param dst           the file to which the path is built.
562    * @param separatorChar the separator for the path components.
563    * @return the relative path, or null if the files have no common ancestor.
564    * @since 5.0.2
565    */
566
567   @Nullable
568   public static String getPath(@NotNull VirtualFile src, @NotNull VirtualFile dst, char separatorChar) {
569     final VirtualFile commonAncestor = getCommonAncestor(src, dst);
570     if (commonAncestor != null) {
571       StringBuilder buffer = new StringBuilder();
572       if (src != commonAncestor) {
573         while (src.getParent() != commonAncestor) {
574           buffer.append("..").append(separatorChar);
575           src = src.getParent();
576           assert src != null;
577         }
578       }
579       buffer.append(getRelativePath(dst, commonAncestor, separatorChar));
580       return buffer.toString();
581     }
582
583     return null;
584   }
585
586   public static boolean isValidName(@NotNull String name) {
587     return name.indexOf('\\') < 0 && name.indexOf('/') < 0;
588   }
589
590   public static String getUrlForLibraryRoot(@NotNull File libraryRoot) {
591     String path = FileUtil.toSystemIndependentName(libraryRoot.getAbsolutePath());
592     if (FileTypeManager.getInstance().getFileTypeByFileName(libraryRoot.getName()) == FileTypes.ARCHIVE) {
593       return VirtualFileManager.constructUrl(JarFileSystem.getInstance().getProtocol(), path + JarFileSystem.JAR_SEPARATOR);
594     }
595     else {
596       return VirtualFileManager.constructUrl(LocalFileSystem.getInstance().getProtocol(), path);
597     }
598   }
599
600   public static VirtualFile createChildSequent(Object requestor, @NotNull VirtualFile dir, @NotNull String prefix, @NotNull String extension) throws IOException {
601     String fileName = prefix + "." + extension;
602     int i = 1;
603     while (dir.findChild(fileName) != null) {
604       fileName = prefix + i + "." + extension;
605       i++;
606     }
607     return dir.createChildData(requestor, fileName);
608   }
609
610   @NotNull
611   public static String[] filterNames(@NotNull String[] names) {
612     int filteredCount = 0;
613     for (String string : names) {
614       if (isBadName(string)) filteredCount++;
615     }
616     if (filteredCount == 0) return names;
617
618     String[] result = ArrayUtil.newStringArray(names.length - filteredCount);
619     int count = 0;
620     for (String string : names) {
621       if (isBadName(string)) continue;
622       result[count++] = string;
623     }
624
625     return result;
626   }
627
628   public static boolean isBadName(String name) {
629     return name == null || name.length() == 0 || "/".equals(name) || "\\".equals(name);
630   }
631
632   public static VirtualFile createDirectories(@NotNull final String dir) throws IOException {
633     final Ref<IOException> err = new Ref<IOException>();
634     VirtualFile result = ApplicationManager.getApplication().runWriteAction(new Computable<VirtualFile>() {
635       public VirtualFile compute() {
636         try {
637           return createDirectoryIfMissing(dir);
638         }
639         catch (IOException e) {
640           err.set(e);
641           return null;
642         }
643       }
644     });
645     if (!err.isNull()) throw err.get();
646     return result;
647   }
648
649   public static VirtualFile createDirectoryIfMissing(VirtualFile parent, String relativePath) throws IOException {
650     for (String each : StringUtil.split(relativePath, "/")) {
651       VirtualFile child = parent.findChild(each);
652       if (child == null) {
653         child = parent.createChildDirectory(LocalFileSystem.getInstance(), each);
654       }
655       parent = child;
656     }
657     return parent;
658   }
659
660   @Nullable
661   public static VirtualFile createDirectoryIfMissing(@NotNull String dir) throws IOException {
662     return doCreateDirectoriesIfMissing(FileUtil.toSystemIndependentName(dir));
663   }
664
665   private static VirtualFile doCreateDirectoriesIfMissing(String dir) throws IOException {
666     final VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByPath(dir);
667     if (file == null) {
668       int pos = dir.lastIndexOf('/');
669       if (pos < 0) return null;
670       VirtualFile parent = createDirectoryIfMissing(dir.substring(0, pos));
671       if (parent == null) return null;
672       final String dirName = dir.substring(pos + 1);
673       return parent.createChildDirectory(LocalFileSystem.getInstance(), dirName);
674     }
675     return file;
676   }
677
678   public static <E extends Throwable> VirtualFile doActionAndRestoreEncoding(@NotNull VirtualFile fileBefore, @NotNull ThrowableComputable<VirtualFile, E> action) throws E {
679     Charset charsetBefore = EncodingManager.getInstance().getEncoding(fileBefore, true);
680     VirtualFile fileAfter = null;
681     try {
682       fileAfter = action.compute();
683       return fileAfter;
684     }
685     finally {
686       if (fileAfter != null) {
687         Charset actual = EncodingManager.getInstance().getEncoding(fileAfter, true);
688         if (!Comparing.equal(actual, charsetBefore)) {
689           EncodingManager.getInstance().setEncoding(fileAfter, charsetBefore);
690         }
691       }
692     }
693   }
694
695   public static void processFileRecursivelyWithoutIgnored(@NotNull final VirtualFile root, @NotNull final Processor<VirtualFile> processor) {
696     final FileTypeManager ftm = FileTypeManager.getInstance();
697     processFilesRecursively(root, processor, new Convertor<VirtualFile, Boolean>() {
698       public Boolean convert(final VirtualFile vf) {
699         return ! ftm.isFileIgnored(vf.getName());
700       }
701     });
702   }
703
704   public static void processFilesRecursively(@NotNull VirtualFile root, @NotNull Processor<VirtualFile> processor,
705                                              @NotNull Convertor<VirtualFile, Boolean> directoryFilter) {
706     if (!processor.process(root)) return;
707
708     if (root.isDirectory() && directoryFilter.convert(root)) {
709       final LinkedList<VirtualFile[]> queue = new LinkedList<VirtualFile[]>();
710
711       queue.add(root.getChildren());
712
713       do {
714         final VirtualFile[] files = queue.removeFirst();
715
716         for (VirtualFile file : files) {
717           if (!processor.process(file)) return;
718           if (file.isDirectory() && directoryFilter.convert(file)) {
719             queue.add(file.getChildren());
720           }
721         }
722       } while (!queue.isEmpty());
723     }
724   }
725
726   public static boolean processFilesRecursively(@NotNull VirtualFile root, @NotNull Processor<VirtualFile> processor) {
727     if (!processor.process(root)) return false;
728
729     if (root.isDirectory()) {
730       final LinkedList<VirtualFile[]> queue = new LinkedList<VirtualFile[]>();
731
732       queue.add(root.getChildren());
733
734       do {
735         final VirtualFile[] files = queue.removeFirst();
736
737         for (VirtualFile file : files) {
738           if (!processor.process(file)) return false;
739           if (file.isDirectory()) {
740             queue.add(file.getChildren());
741           }
742         }
743       } while (!queue.isEmpty());
744     }
745
746     return true;
747   }
748
749   @Nullable
750   public static <T> T processInputStream(@NotNull final VirtualFile file, @NotNull Function<InputStream, T> function) {
751     InputStream stream = null;
752     try {
753       stream = file.getInputStream();
754       return function.fun(stream);
755     }
756     catch (IOException e) {
757       LOG.error(e);
758     } finally {
759       try {
760         if (stream != null) {
761           stream.close();
762         }
763       }
764       catch (IOException e) {
765         LOG.error(e);
766       }
767     }
768     return null;
769   }
770
771   @NotNull
772   public static VirtualFile[] toVirtualFileArray(@NotNull Collection<? extends VirtualFile> files) {
773     int size = files.size();
774     if (size == 0) return VirtualFile.EMPTY_ARRAY;
775     return files.toArray(new VirtualFile[size]);
776   }
777 }