IDEADEV-29757 (NPE: CvsInfo.getParentFile)
[fedora-idea.git] / platform-api / src / com / intellij / openapi / vfs / VfsUtil.java
blob18629f6b72bbb3e066aedfbe6fe31de8ba52def5
1 /*
2 * Copyright 2000-2007 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.openapi.vfs;
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.fs.FileSystem;
32 import com.intellij.util.io.fs.IFile;
33 import gnu.trove.THashSet;
34 import org.jetbrains.annotations.NonNls;
35 import org.jetbrains.annotations.NotNull;
36 import org.jetbrains.annotations.Nullable;
38 import java.io.*;
39 import java.net.MalformedURLException;
40 import java.net.URL;
41 import java.nio.charset.Charset;
42 import java.util.*;
44 public class VfsUtil {
45 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.VfsUtil");
47 public static String loadText(@NotNull VirtualFile file) throws IOException{
48 InputStreamReader reader = new InputStreamReader(file.getInputStream(), file.getCharset());
49 try {
50 return new String(FileUtil.loadText(reader, (int)file.getLength()));
52 finally {
53 reader.close();
57 public static void saveText(VirtualFile file, String text) throws IOException {
58 file.setBinaryContent(text.getBytes(file.getCharset().name()));
61 /**
62 * Checks whether the <code>ancestor {@link VirtualFile}</code> is parent of <code>file
63 * {@link VirtualFile}</code>.
65 * @param ancestor the file
66 * @param file the file
67 * @param strict if <code>false</code> then this method returns <code>true</code> if <code>ancestor</code>
68 * and <code>file</code> are equal
69 * @return <code>true</code> if <code>ancestor</code> is parent of <code>file</code>; <code>false</code> otherwise
71 public static boolean isAncestor(@NotNull VirtualFile ancestor, @NotNull VirtualFile file, boolean strict) {
72 if (!file.getFileSystem().equals(ancestor.getFileSystem())) return false;
73 VirtualFile parent = strict ? file.getParent() : file;
74 while (true) {
75 if (parent == null) return false;
76 if (parent.equals(ancestor)) return true;
77 parent = parent.getParent();
81 /**
82 * Gets the relative path of <code>file</code> to its <code>ancestor</code>. Uses <code>separator</code> for
83 * separating files.
85 * @param file the file
86 * @param ancestor parent file
87 * @param separator character to use as files separator
88 * @return the relative path
90 public static String getRelativePath(VirtualFile file, VirtualFile ancestor, char separator) {
91 if (!file.getFileSystem().equals(ancestor.getFileSystem())) return null;
93 int length = 0;
94 VirtualFile parent = file;
95 while (true) {
96 if (parent == null) return null;
97 if (parent.equals(ancestor)) break;
98 if (length > 0) {
99 length++;
101 length += parent.getName().length();
102 parent = parent.getParent();
105 char[] chars = new char[length];
106 int index = chars.length;
107 parent = file;
108 while (true) {
109 if (parent.equals(ancestor)) break;
110 if (index < length) {
111 chars[--index] = separator;
113 String name = parent.getName();
114 for (int i = name.length() - 1; i >= 0; i--) {
115 chars[--index] = name.charAt(i);
117 parent = parent.getParent();
119 return new String(chars);
123 * Copies all files matching the <code>filter</code> from <code>fromDir</code> to <code>toDir</code>.
125 * @param requestor any object to control who called this method. Note that
126 * it is considered to be an external change if <code>requestor</code> is <code>null</code>.
127 * See {@link VirtualFileEvent#getRequestor}
128 * @param fromDir the directory to copy from
129 * @param toDir the directory to copy to
130 * @param filter {@link VirtualFileFilter}
131 * @throws IOException if files failed to be copied
133 public static void copyDirectory(Object requestor, VirtualFile fromDir, VirtualFile toDir, @Nullable VirtualFileFilter filter)
134 throws IOException {
135 VirtualFile[] children = fromDir.getChildren();
136 for (VirtualFile child : children) {
137 if (filter == null || filter.accept(child)) {
138 if (!child.isDirectory()) {
139 copyFile(requestor, child, toDir);
141 else {
142 VirtualFile newChild = toDir.createChildDirectory(requestor, child.getName());
143 copyDirectory(requestor, child, newChild, filter);
150 * Makes a copy of the <code>file</code> in the <code>toDir</code> folder and returns it.
152 * @param requestor any object to control who called this method. Note that
153 * it is considered to be an external change if <code>requestor</code> is <code>null</code>.
154 * See {@link VirtualFileEvent#getRequestor}
155 * @param file file to make a copy of
156 * @param toDir directory to make a copy in
157 * @return a copy of the file
158 * @throws IOException if file failed to be copied
160 public static VirtualFile copyFile(Object requestor, VirtualFile file, VirtualFile toDir) throws IOException {
161 return copyFile(requestor, file, toDir, file.getName());
165 * Makes a copy of the <code>file</code> in the <code>toDir</code> folder with the <code>newName</code> and returns it.
167 * @param requestor any object to control who called this method. Note that
168 * it is considered to be an external change if <code>requestor</code> is <code>null</code>.
169 * See {@link VirtualFileEvent#getRequestor}
170 * @param file file to make a copy of
171 * @param toDir directory to make a copy in
172 * @param newName new name of the file
173 * @return a copy of the file
174 * @throws IOException if file failed to be copied
176 public static VirtualFile copyFile(Object requestor, VirtualFile file, VirtualFile toDir, @NonNls String newName)
177 throws IOException {
178 final VirtualFile newChild = toDir.createChildData(requestor, newName);
179 // [jeka] TODO: to be duscussed if the copy should have the same timestamp as the original
180 //OutputStream out = newChild.getOutputStream(requestor, -1, file.getActualTimeStamp());
181 newChild.setBinaryContent(file.contentsToByteArray());
182 return newChild;
186 * Copies content of resource to the given file
188 * @param file to copy to
189 * @param resourceUrl url of the resource to be copied
190 * @throws java.io.IOException if resource not found or copying failed
192 public static void copyFromResource(VirtualFile file, @NonNls String resourceUrl) throws IOException {
193 InputStream out = VfsUtil.class.getResourceAsStream(resourceUrl);
194 if (out == null) {
195 throw new FileNotFoundException(resourceUrl);
197 try {
198 byte[] bytes = FileUtil.adaptiveLoadBytes(out);
199 file.setBinaryContent(bytes);
200 } finally {
201 out.close();
206 * Gets the array of common ancestors for passed files.
208 * @param files array of files
209 * @return array of common ancestors for passed files
211 public static VirtualFile[] getCommonAncestors(VirtualFile[] files) {
212 // Separate files by first component in the path.
213 HashMap<VirtualFile,Set<VirtualFile>> map = new HashMap<VirtualFile, Set<VirtualFile>>();
214 for (VirtualFile aFile : files) {
215 VirtualFile directory = aFile.isDirectory() ? aFile : aFile.getParent();
216 if (directory == null) return VirtualFile.EMPTY_ARRAY;
217 VirtualFile[] path = getPathComponents(directory);
218 Set<VirtualFile> filesSet;
219 final VirtualFile firstPart = path[0];
220 if (map.containsKey(firstPart)) {
221 filesSet = map.get(firstPart);
223 else {
224 filesSet = new THashSet<VirtualFile>();
225 map.put(firstPart, filesSet);
227 filesSet.add(directory);
229 // Find common ancestor for each set of files.
230 ArrayList<VirtualFile> ancestorsList = new ArrayList<VirtualFile>();
231 for (Set<VirtualFile> filesSet : map.values()) {
232 VirtualFile ancestor = null;
233 for (VirtualFile file : filesSet) {
234 if (ancestor == null) {
235 ancestor = file;
236 continue;
238 ancestor = getCommonAncestor(ancestor, file);
239 //assertTrue(ancestor != null);
241 ancestorsList.add(ancestor);
242 filesSet.clear();
244 return ancestorsList.toArray(new VirtualFile[ancestorsList.size()]);
248 * Gets the common ancestor for passed files, or null if the files do not have common ancestors.
250 * @param file1 fist file
251 * @param file2 second file
252 * @return common ancestor for the passed files. Returns <code>null</code> if
253 * the files do not have common ancestor
255 public static VirtualFile getCommonAncestor(VirtualFile file1, VirtualFile file2) {
256 if (!file1.getFileSystem().equals(file2.getFileSystem())) {
257 return null;
260 VirtualFile[] path1 = getPathComponents(file1);
261 VirtualFile[] path2 = getPathComponents(file2);
263 VirtualFile[] minLengthPath;
264 VirtualFile[] maxLengthPath;
265 if (path1.length < path2.length) {
266 minLengthPath = path1;
267 maxLengthPath = path2;
269 else {
270 minLengthPath = path2;
271 maxLengthPath = path1;
274 int lastEqualIdx = -1;
275 for (int i = 0; i < minLengthPath.length; i++) {
276 if (minLengthPath[i].equals(maxLengthPath[i])) {
277 lastEqualIdx = i;
279 else {
280 break;
283 return lastEqualIdx == -1 ? null : minLengthPath[lastEqualIdx];
287 * Gets an array of files representing paths from root to the passed file.
289 * @param file the file
290 * @return virtual files which represents paths from root to the passed file
292 private static VirtualFile[] getPathComponents(VirtualFile file) {
293 ArrayList<VirtualFile> componentsList = new ArrayList<VirtualFile>();
294 while (file != null) {
295 componentsList.add(file);
296 file = file.getParent();
298 int size = componentsList.size();
299 VirtualFile[] components = new VirtualFile[size];
300 for (int i = 0; i < size; i++) {
301 components[i] = componentsList.get(size - i - 1);
303 return components;
306 @SuppressWarnings({"HardCodedStringLiteral"})
307 @Nullable
308 public static VirtualFile findRelativeFile(String uri, VirtualFile base) {
309 if (base != null) {
310 if (!base.isValid()){
311 LOG.error("Invalid file name: " + base.getName() + ", url: " + uri);
315 uri = uri.replace('\\', '/');
317 if (uri.startsWith("file:///")) {
318 uri = uri.substring("file:///".length());
319 if (!SystemInfo.isWindows) uri = "/" + uri;
321 else if (uri.startsWith("file:/")) {
322 uri = uri.substring("file:/".length());
323 if (!SystemInfo.isWindows) uri = "/" + uri;
325 else if (uri.startsWith("file:")) {
326 uri = uri.substring("file:".length());
329 VirtualFile file = null;
331 if (uri.startsWith("jar:file:/")) {
332 uri = uri.substring("jar:file:/".length());
333 if (!SystemInfo.isWindows) uri = "/" + uri;
334 file = VirtualFileManager.getInstance().findFileByUrl(JarFileSystem.PROTOCOL_PREFIX + uri);
336 else {
337 if (!SystemInfo.isWindows && StringUtil.startsWithChar(uri, '/')) {
338 file = LocalFileSystem.getInstance().findFileByPath(uri);
340 else if (SystemInfo.isWindows && uri.length() >= 2 && Character.isLetter(uri.charAt(0)) && uri.charAt(1) == ':') {
341 file = LocalFileSystem.getInstance().findFileByPath(uri);
345 if (file == null && uri.contains(JarFileSystem.JAR_SEPARATOR)) {
346 file = JarFileSystem.getInstance().findFileByPath(uri);
349 if (file == null) {
350 if (base == null) return LocalFileSystem.getInstance().findFileByPath(uri);
351 if (!base.isDirectory()) base = base.getParent();
352 if (base == null) return LocalFileSystem.getInstance().findFileByPath(uri);
353 file = VirtualFileManager.getInstance().findFileByUrl(base.getUrl() + "/" + uri);
354 if (file == null) return null;
357 return file;
360 @NonNls private static final String FILE = "file";
361 @NonNls private static final String JAR = "jar";
362 @NonNls private static final String MAILTO = "mailto";
363 private static final String PROTOCOL_DELIMITER = ":";
366 * Searches for the file specified by given java,net.URL.
367 * Note that this method currently tested only for "file" and "jar" protocols under Unix and Windows
369 * @param url the URL to find file by
370 * @return <code>{@link VirtualFile}</code> if the file was found, <code>null</code> otherwise
372 public static VirtualFile findFileByURL(URL url) {
373 VirtualFileManager virtualFileManager = VirtualFileManager.getInstance();
374 return findFileByURL(url, virtualFileManager);
377 public static VirtualFile findFileByURL(URL url, VirtualFileManager virtualFileManager) {
378 String vfUrl = convertFromUrl(url);
379 return virtualFileManager.findFileByUrl(vfUrl);
383 * Converts VsfUrl info java.net.URL. Does not support "jar:" protocol.
385 * @param vfsUrl VFS url (as constructed by VfsFile.getUrl())
386 * @return converted URL or null if error has occured
389 @Nullable
390 public static URL convertToURL(String vfsUrl) {
391 if (vfsUrl.startsWith(JAR)) {
392 LOG.error("jar: protocol not supported.");
393 return null;
396 // [stathik] for supporting mail URLs in Plugin Manager
397 if (vfsUrl.startsWith(MAILTO)) {
398 try {
399 return new URL (vfsUrl);
401 catch (MalformedURLException e) {
402 return null;
406 String[] split = vfsUrl.split("://");
408 if (split.length != 2) {
409 LOG.debug("Malformed VFS URL: " + vfsUrl);
410 return null;
413 String protocol = split[0];
414 String path = split[1];
416 try {
417 if (protocol.equals(FILE)) {
418 return new URL(protocol, "", path);
420 else {
421 return new URL(vfsUrl);
424 catch (MalformedURLException e) {
425 LOG.debug("MalformedURLException occured:" + e.getMessage());
426 return null;
430 private static String convertFromUrl(URL url) {
431 String protocol = url.getProtocol();
432 String path = url.getPath();
433 if (protocol.equals(JAR)) {
434 if (StringUtil.startsWithConcatenationOf(path, FILE, PROTOCOL_DELIMITER)) {
435 try {
436 URL subURL = new URL(path);
437 path = subURL.getPath();
439 catch (MalformedURLException e) {
440 throw new RuntimeException(VfsBundle.message("url.parse.unhandled.exception"), e);
443 else {
444 throw new RuntimeException(new IOException(VfsBundle.message("url.parse.error", url.toExternalForm())));
447 if (SystemInfo.isWindows || SystemInfo.isOS2) {
448 while (path.length() > 0 && path.charAt(0) == '/') {
449 path = path.substring(1, path.length());
453 path = StringUtil.replace(path, "%20", " ");
454 return protocol + "://" + path;
457 public static String urlToPath(@NonNls String url) {
458 if (url == null) return "";
459 return VirtualFileManager.extractPath(url);
462 public static String pathToUrl(@NotNull String path) {
463 return VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, path);
466 public static File virtualToIoFile(@NotNull VirtualFile file) {
467 return new File(PathUtil.toPresentableUrl(file.getUrl()));
470 public static IFile virtualToIFile(VirtualFile file) {
471 return FileSystem.FILE_SYSTEM.createFile(PathUtil.toPresentableUrl(file.getUrl()));
474 public static VirtualFile copyFileRelative(Object requestor, VirtualFile file, VirtualFile toDir, String relativePath) throws IOException {
475 StringTokenizer tokenizer = new StringTokenizer(relativePath,"/");
476 VirtualFile curDir = toDir;
478 while (true) {
479 String token = tokenizer.nextToken();
480 if (tokenizer.hasMoreTokens()) {
481 VirtualFile childDir = curDir.findChild(token);
482 if (childDir == null) {
483 childDir = curDir.createChildDirectory(requestor, token);
485 curDir = childDir;
487 else {
488 return copyFile(requestor, file, curDir, token);
493 public static String fixIDEAUrl( String ideaUrl ) {
494 int idx = ideaUrl.indexOf("://");
495 if( idx >= 0 ) {
496 String s = ideaUrl.substring(0, idx);
498 if (s.equals(JarFileSystem.PROTOCOL)) {
499 //noinspection HardCodedStringLiteral
500 s = "jar:file";
502 ideaUrl = s+":/"+ideaUrl.substring(idx+3);
504 return ideaUrl;
507 public static String fixURLforIDEA( String url ) {
508 int idx = url.indexOf(":/");
509 if( idx >= 0 && idx+2 < url.length() && url.charAt(idx+2) != '/' ) {
510 String prefix = url.substring(0, idx);
511 String suffix = url.substring(idx+2);
513 if (SystemInfo.isWindows) {
514 url = prefix+"://"+suffix;
515 } else {
516 url = prefix+":///"+suffix;
519 return url;
522 public static boolean isAncestor(File ancestor, File file, boolean strict) {
523 File parent = strict ? file.getParentFile() : file;
524 while (parent != null) {
525 if (parent.equals(ancestor)) return true;
526 parent = parent.getParentFile();
529 return false;
533 * Returns the relative path from one virtual file to another.
535 * @param src the file from which the relative path is built.
536 * @param dst the file to which the path is built.
537 * @param separatorChar the separator for the path components.
538 * @return the relative path, or null if the files have no common ancestor.
539 * @since 5.0.2
542 @Nullable
543 public static String getPath(VirtualFile src, VirtualFile dst, char separatorChar) {
544 final VirtualFile commonAncestor = getCommonAncestor(src, dst);
545 if (commonAncestor != null) {
546 StringBuilder buffer = new StringBuilder();
547 if (src != commonAncestor) {
548 while (src.getParent() != commonAncestor) {
549 buffer.append("..").append(separatorChar);
550 src = src.getParent();
551 assert src != null;
554 buffer.append(getRelativePath(dst, commonAncestor, separatorChar));
555 return buffer.toString();
558 return null;
561 public static boolean isValidName(String name) {
562 return name.indexOf('\\') < 0 && name.indexOf('/') < 0;
565 public static String getUrlForLibraryRoot(File libraryRoot) {
566 String path = FileUtil.toSystemIndependentName(libraryRoot.getAbsolutePath());
567 if (FileTypeManager.getInstance().getFileTypeByFileName(libraryRoot.getName()) == FileTypes.ARCHIVE) {
568 return VirtualFileManager.constructUrl(JarFileSystem.getInstance().getProtocol(), path + JarFileSystem.JAR_SEPARATOR);
570 else {
571 return VirtualFileManager.constructUrl(LocalFileSystem.getInstance().getProtocol(), path);
575 public static VirtualFile createChildSequent(Object requestor, VirtualFile dir, String prefix, String extension) throws IOException {
576 String fileName = prefix + "." + extension;
577 int i = 1;
578 while (dir.findChild(fileName) != null) {
579 fileName = prefix + i + "." + extension;
580 i++;
582 return dir.createChildData(requestor, fileName);
585 public static String[] filterNames(String[] names) {
586 int filteredCount = 0;
587 for (String string : names) {
588 if (isBadName(string)) filteredCount++;
590 if (filteredCount == 0) return names;
592 String[] result = ArrayUtil.newStringArray(names.length - filteredCount);
593 int count = 0;
594 for (String string : names) {
595 if (isBadName(string)) continue;
596 result[count++] = string;
599 return result;
602 public static boolean isBadName(final String name) {
603 return name == null || name.length() == 0 || "/".equals(name) || "\\".equals(name);
606 public static VirtualFile createDirectories(final String dir) throws IOException {
607 final String path = FileUtil.toSystemIndependentName(dir);
608 final Ref<IOException> err = new Ref<IOException>();
609 VirtualFile result = ApplicationManager.getApplication().runWriteAction(new Computable<VirtualFile>() {
610 public VirtualFile compute() {
611 try {
612 return createDirectoryIfMissing(path);
614 catch (IOException e) {
615 err.set(e);
616 return null;
620 if (!err.isNull()) throw err.get();
621 return result;
624 @Nullable
625 public static VirtualFile createDirectoryIfMissing(final String dir) throws IOException {
626 final VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByPath(dir);
627 if (file == null) {
628 int pos = dir.lastIndexOf('/');
629 if (pos < 0) return null;
630 VirtualFile parent = createDirectoryIfMissing(dir.substring(0, pos));
631 if (parent == null) return null;
632 final String dirName = dir.substring(pos + 1);
633 return parent.createChildDirectory(LocalFileSystem.getInstance(), dirName);
635 return file;
638 public static <E extends Throwable> VirtualFile doActionAndRestoreEncoding(VirtualFile fileBefore, ThrowableComputable<VirtualFile, E> action) throws E {
639 Charset charsetBefore = EncodingManager.getInstance().getEncoding(fileBefore, true);
640 VirtualFile fileAfter = null;
641 try {
642 fileAfter = action.compute();
643 return fileAfter;
645 finally {
646 if (fileAfter != null) {
647 Charset actual = EncodingManager.getInstance().getEncoding(fileAfter, true);
648 if (!Comparing.equal(actual, charsetBefore)) {
649 EncodingManager.getInstance().setEncoding(fileAfter, charsetBefore);
655 public static void processFileRecursivelyWithoutIgnored(final VirtualFile root, final Processor<VirtualFile> processor) {
656 final FileTypeManager ftm = FileTypeManager.getInstance();
657 processFilesRecursively(root, processor, new Convertor<VirtualFile, Boolean>() {
658 public Boolean convert(final VirtualFile vf) {
659 return ! ftm.isFileIgnored(vf.getName());
664 public static void processFilesRecursively(final VirtualFile root, final Processor<VirtualFile> processor,
665 final Convertor<VirtualFile, Boolean> directoryFilter) {
666 final LinkedList<VirtualFile> queue = new LinkedList<VirtualFile>();
667 queue.add(root);
668 while (!queue.isEmpty()) {
669 final VirtualFile file = queue.removeFirst();
670 if (!processor.process(file)) return;
671 if (file.isDirectory() && directoryFilter.convert(file)) {
672 queue.addAll(Arrays.asList(file.getChildren()));
677 public static boolean processFilesRecursively(final VirtualFile root, final Processor<VirtualFile> processor) {
678 final LinkedList<VirtualFile> queue = new LinkedList<VirtualFile>();
679 queue.add(root);
680 while (!queue.isEmpty()) {
681 final VirtualFile file = queue.removeFirst();
682 if (!processor.process(file)) return false;
683 if (file.isDirectory()) {
684 queue.addAll(Arrays.asList(file.getChildren()));
687 return true;
690 @Nullable
691 public static <T> T processInputStream(@NotNull final VirtualFile file, final Function<InputStream, T> function) {
692 InputStream stream = null;
693 try {
694 stream = file.getInputStream();
695 return function.fun(stream);
697 catch (IOException e) {
698 LOG.error(e);
699 } finally {
700 try {
701 if (stream != null) {
702 stream.close();
705 catch (IOException e) {
706 LOG.error(e);
709 return null;