/* * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.openapi.vfs; import com.intellij.openapi.application.Result; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.fileTypes.FileTypes; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.newvfs.NewVirtualFile; import com.intellij.util.ArrayUtil; import com.intellij.util.Function; import com.intellij.util.Processor; import com.intellij.util.SystemProperties; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.Convertor; import com.intellij.util.lang.UrlClassLoader; import gnu.trove.THashSet; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.Charset; import java.util.*; public class VfsUtil extends VfsUtilCore { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.VfsUtil"); public static final char VFS_PATH_SEPARATOR = '/'; public static void saveText(@NotNull VirtualFile file, @NotNull String text) throws IOException { Charset charset = file.getCharset(); file.setBinaryContent(text.getBytes(charset.name())); } /** * Copies all files matching the filter from fromDir to toDir. * Symlinks end special files are ignored. * * @param requestor any object to control who called this method. Note that * it is considered to be an external change if requestor is null. * See {@link VirtualFileEvent#getRequestor} * @param fromDir the directory to copy from * @param toDir the directory to copy to * @param filter {@link VirtualFileFilter} * @throws IOException if files failed to be copied */ public static void copyDirectory(Object requestor, @NotNull VirtualFile fromDir, @NotNull VirtualFile toDir, @Nullable VirtualFileFilter filter) throws IOException { @SuppressWarnings("UnsafeVfsRecursion") VirtualFile[] children = fromDir.getChildren(); for (VirtualFile child : children) { if (!child.is(VFileProperty.SYMLINK) && !child.is(VFileProperty.SPECIAL) && (filter == null || filter.accept(child))) { if (!child.isDirectory()) { copyFile(requestor, child, toDir); } else { VirtualFile newChild = toDir.findChild(child.getName()); if (newChild == null) { newChild = toDir.createChildDirectory(requestor, child.getName()); } copyDirectory(requestor, child, newChild, filter); } } } } /** * Copies content of resource to the given file * * @param file to copy to * @param resourceUrl url of the resource to be copied * @throws java.io.IOException if resource not found or copying failed */ public static void copyFromResource(@NotNull VirtualFile file, @NonNls @NotNull String resourceUrl) throws IOException { InputStream out = VfsUtil.class.getResourceAsStream(resourceUrl); if (out == null) { throw new FileNotFoundException(resourceUrl); } try { byte[] bytes = FileUtil.adaptiveLoadBytes(out); file.setBinaryContent(bytes); } finally { out.close(); } } /** * Makes a copy of the file in the toDir folder and returns it. * Handles both files and directories. * * @param requestor any object to control who called this method. Note that * it is considered to be an external change if requestor is null. * See {@link VirtualFileEvent#getRequestor} * @param file file or directory to make a copy of * @param toDir directory to make a copy in * @return a copy of the file * @throws IOException if file failed to be copied */ public static VirtualFile copy(Object requestor, @NotNull VirtualFile file, @NotNull VirtualFile toDir) throws IOException { if (file.isDirectory()) { VirtualFile newDir = toDir.createChildDirectory(requestor, file.getName()); copyDirectory(requestor, file, newDir, null); return newDir; } else { return copyFile(requestor, file, toDir); } } /** * Gets the array of common ancestors for passed files. * * @param files array of files * @return array of common ancestors for passed files */ @NotNull public static VirtualFile[] getCommonAncestors(@NotNull VirtualFile[] files) { // Separate files by first component in the path. HashMap> map = new HashMap>(); for (VirtualFile aFile : files) { VirtualFile directory = aFile.isDirectory() ? aFile : aFile.getParent(); if (directory == null) return VirtualFile.EMPTY_ARRAY; VirtualFile[] path = getPathComponents(directory); Set filesSet; final VirtualFile firstPart = path[0]; if (map.containsKey(firstPart)) { filesSet = map.get(firstPart); } else { filesSet = new THashSet(); map.put(firstPart, filesSet); } filesSet.add(directory); } // Find common ancestor for each set of files. ArrayList ancestorsList = new ArrayList(); for (Set filesSet : map.values()) { VirtualFile ancestor = null; for (VirtualFile file : filesSet) { if (ancestor == null) { ancestor = file; continue; } ancestor = getCommonAncestor(ancestor, file); //assertTrue(ancestor != null); } ancestorsList.add(ancestor); filesSet.clear(); } return toVirtualFileArray(ancestorsList); } /** * Gets the common ancestor for passed files, or {@code null} if the files do not have common ancestors. */ @Nullable public static VirtualFile getCommonAncestor(@NotNull Collection files) { VirtualFile ancestor = null; for (VirtualFile file : files) { if (ancestor == null) { ancestor = file; } else { ancestor = getCommonAncestor(ancestor, file); if (ancestor == null) return null; } } return ancestor; } @Nullable public static VirtualFile findRelativeFile(@Nullable VirtualFile base, String ... path) { VirtualFile file = base; for (String pathElement : path) { if (file == null) return null; if ("..".equals(pathElement)) { file = file.getParent(); } else { file = file.findChild(pathElement); } } return file; } @NonNls private static final String MAILTO = "mailto"; /** * Searches for the file specified by given java,net.URL. * Note that this method currently tested only for "file" and "jar" protocols under Unix and Windows * * @param url the URL to find file by * @return {@link VirtualFile} if the file was found, null otherwise */ public static VirtualFile findFileByURL(@NotNull URL url) { VirtualFileManager virtualFileManager = VirtualFileManager.getInstance(); return findFileByURL(url, virtualFileManager); } public static VirtualFile findFileByURL(@NotNull URL url, @NotNull VirtualFileManager virtualFileManager) { String vfUrl = convertFromUrl(url); return virtualFileManager.findFileByUrl(vfUrl); } @Nullable public static VirtualFile findFileByIoFile(@NotNull File file, boolean refreshIfNeeded) { LocalFileSystem fileSystem = LocalFileSystem.getInstance(); VirtualFile virtualFile = fileSystem.findFileByIoFile(file); if (refreshIfNeeded && (virtualFile == null || !virtualFile.isValid())) { virtualFile = fileSystem.refreshAndFindFileByIoFile(file); } return virtualFile; } /** * Converts VsfUrl info java.net.URL. Does not support "jar:" protocol. * * @param vfsUrl VFS url (as constructed by VfsFile.getUrl()) * @return converted URL or null if error has occured */ @Nullable public static URL convertToURL(@NotNull String vfsUrl) { if (vfsUrl.startsWith(StandardFileSystems.JAR_PROTOCOL)) { LOG.error("jar: protocol not supported."); return null; } // [stathik] for supporting mail URLs in Plugin Manager if (vfsUrl.startsWith(MAILTO)) { try { return new URL (vfsUrl); } catch (MalformedURLException e) { return null; } } String[] split = vfsUrl.split("://"); if (split.length != 2) { LOG.debug("Malformed VFS URL: " + vfsUrl); return null; } String protocol = split[0]; String path = split[1]; try { if (protocol.equals(StandardFileSystems.FILE_PROTOCOL)) { return new URL(StandardFileSystems.FILE_PROTOCOL, "", path); } else { return UrlClassLoader.internProtocol(new URL(vfsUrl)); } } catch (MalformedURLException e) { LOG.debug("MalformedURLException occurred:" + e.getMessage()); return null; } } public static VirtualFile copyFileRelative(Object requestor, @NotNull VirtualFile file, @NotNull VirtualFile toDir, @NotNull String relativePath) throws IOException { StringTokenizer tokenizer = new StringTokenizer(relativePath,"/"); VirtualFile curDir = toDir; while (true) { String token = tokenizer.nextToken(); if (tokenizer.hasMoreTokens()) { VirtualFile childDir = curDir.findChild(token); if (childDir == null) { childDir = curDir.createChildDirectory(requestor, token); } curDir = childDir; } else { return copyFile(requestor, file, curDir, token); } } } @NotNull public static String toIdeaUrl(@NotNull String url) { return toIdeaUrl(url, true); } /** * @return correct URL, must be used only for external communication */ @NotNull public static URI toUri(@NotNull VirtualFile file) { String path = file.getPath(); try { if (file.isInLocalFileSystem()) { if (SystemInfo.isWindows && path.charAt(0) != '/') { path = '/' + path; } return new URI(file.getFileSystem().getProtocol(), "", path, null, null); } return new URI(file.getFileSystem().getProtocol(), path, null); } catch (URISyntaxException e) { throw new IllegalArgumentException(e); } } /** * @return correct URL, must be used only for external communication */ @NotNull public static URI toUri(@NotNull File file) { String path = file.toURI().getPath(); try { if (SystemInfo.isWindows && path.charAt(0) != '/') { path = '/' + path; } return new URI(StandardFileSystems.FILE_PROTOCOL, "", path, null, null); } catch (URISyntaxException e) { throw new IllegalArgumentException(e); } } /** * uri - may be incorrect (escaping or missed "/" before disk name under windows), may be not fully encoded, * may contains query and fragment * @return correct URI, must be used only for external communication */ @Nullable public static URI toUri(@NonNls @NotNull String uri) { int index = uri.indexOf("://"); if (index < 0) { // true URI, like mailto: try { return new URI(uri); } catch (URISyntaxException e) { LOG.debug(e); return null; } } if (SystemInfo.isWindows && uri.startsWith(LocalFileSystem.PROTOCOL_PREFIX)) { int firstSlashIndex = index + "://".length(); if (uri.charAt(firstSlashIndex) != '/') { uri = LocalFileSystem.PROTOCOL_PREFIX + '/' + uri.substring(firstSlashIndex); } } try { return new URI(uri); } catch (URISyntaxException e) { LOG.debug("uri is not fully encoded", e); // so, uri is not fully encoded (space) try { int fragmentIndex = uri.lastIndexOf('#'); String path = uri.substring(index + 1, fragmentIndex > 0 ? fragmentIndex : uri.length()); String fragment = fragmentIndex > 0 ? uri.substring(fragmentIndex + 1) : null; return new URI(uri.substring(0, index), path, fragment); } catch (URISyntaxException e1) { LOG.debug(e1); return null; } } } /** * Returns the relative path from one virtual file to another. * * @param src the file from which the relative path is built. * @param dst the file to which the path is built. * @param separatorChar the separator for the path components. * @return the relative path, or null if the files have no common ancestor. * @since 5.0.2 */ @Nullable public static String getPath(@NotNull VirtualFile src, @NotNull VirtualFile dst, char separatorChar) { final VirtualFile commonAncestor = getCommonAncestor(src, dst); if (commonAncestor != null) { StringBuilder buffer = new StringBuilder(); if (!Comparing.equal(src, commonAncestor)) { while (!Comparing.equal(src.getParent(), commonAncestor)) { buffer.append("..").append(separatorChar); src = src.getParent(); } } buffer.append(getRelativePath(dst, commonAncestor, separatorChar)); return buffer.toString(); } return null; } public static String getUrlForLibraryRoot(@NotNull File libraryRoot) { String path = FileUtil.toSystemIndependentName(libraryRoot.getAbsolutePath()); if (FileTypeManager.getInstance().getFileTypeByFileName(libraryRoot.getName()) == FileTypes.ARCHIVE) { return VirtualFileManager.constructUrl(JarFileSystem.getInstance().getProtocol(), path + JarFileSystem.JAR_SEPARATOR); } else { return VirtualFileManager.constructUrl(LocalFileSystem.getInstance().getProtocol(), path); } } public static VirtualFile createChildSequent(Object requestor, @NotNull VirtualFile dir, @NotNull String prefix, @NotNull String extension) throws IOException { String fileName = prefix + "." + extension; int i = 1; while (dir.findChild(fileName) != null) { fileName = prefix + i + "." + extension; i++; } return dir.createChildData(requestor, fileName); } @NotNull public static String[] filterNames(@NotNull String[] names) { int filteredCount = 0; for (String string : names) { if (isBadName(string)) filteredCount++; } if (filteredCount == 0) return names; String[] result = ArrayUtil.newStringArray(names.length - filteredCount); int count = 0; for (String string : names) { if (isBadName(string)) continue; result[count++] = string; } return result; } public static boolean isBadName(String name) { return name == null || name.isEmpty() || "/".equals(name) || "\\".equals(name); } public static VirtualFile createDirectories(@NotNull final String directoryPath) throws IOException { return new WriteAction() { @Override protected void run(Result result) throws Throwable { VirtualFile res = createDirectoryIfMissing(directoryPath); result.setResult(res); } }.execute().throwException().getResultObject(); } public static VirtualFile createDirectoryIfMissing(VirtualFile parent, String relativePath) throws IOException { for (String each : StringUtil.split(relativePath, "/")) { VirtualFile child = parent.findChild(each); if (child == null) { child = parent.createChildDirectory(LocalFileSystem.getInstance(), each); } parent = child; } return parent; } @Nullable public static VirtualFile createDirectoryIfMissing(@NotNull String directoryPath) throws IOException { String path = FileUtil.toSystemIndependentName(directoryPath); final VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByPath(path); if (file == null) { int pos = path.lastIndexOf('/'); if (pos < 0) return null; VirtualFile parent = createDirectoryIfMissing(path.substring(0, pos)); if (parent == null) return null; final String dirName = path.substring(pos + 1); return parent.createChildDirectory(LocalFileSystem.getInstance(), dirName); } return file; } /** * Returns all files in some virtual files recursively * @param root virtual file to get descendants * @return descendants */ @NotNull public static List collectChildrenRecursively(@NotNull final VirtualFile root) { final List result = new ArrayList(); processFilesRecursively(root, new Processor() { @Override public boolean process(final VirtualFile t) { result.add(t); return true; } }); return result; } public static void processFileRecursivelyWithoutIgnored(@NotNull final VirtualFile root, @NotNull final Processor processor) { final FileTypeManager ftm = FileTypeManager.getInstance(); processFilesRecursively(root, processor, new Convertor() { public Boolean convert(final VirtualFile vf) { return ! ftm.isFileIgnored(vf); } }); } @Nullable public static T processInputStream(@NotNull final VirtualFile file, @NotNull Function function) { InputStream stream = null; try { stream = file.getInputStream(); return function.fun(stream); } catch (IOException e) { LOG.error(e); } finally { try { if (stream != null) { stream.close(); } } catch (IOException e) { LOG.error(e); } } return null; } @NotNull public static String getReadableUrl(@NotNull final VirtualFile file) { String url = null; if (file.isInLocalFileSystem()) { url = file.getPresentableUrl(); } if (url == null) { url = file.getUrl(); } return url; } @Nullable public static VirtualFile getUserHomeDir() { final String path = SystemProperties.getUserHome(); return LocalFileSystem.getInstance().findFileByPath(FileUtil.toSystemIndependentName(path)); } @NotNull public static VirtualFile[] getChildren(@NotNull VirtualFile dir) { VirtualFile[] children = dir.getChildren(); return children == null ? VirtualFile.EMPTY_ARRAY : children; } /** * @param url Url for virtual file * @return url for parent directory of virtual file */ @Nullable public static String getParentDir(@Nullable final String url) { if (url == null) { return null; } final int index = url.lastIndexOf(VFS_PATH_SEPARATOR); return index < 0 ? null : url.substring(0, index); } /** * @param urlOrPath Url for virtual file * @return file name */ @Nullable public static String extractFileName(@Nullable final String urlOrPath) { if (urlOrPath == null) { return null; } final int index = urlOrPath.lastIndexOf(VFS_PATH_SEPARATOR); return index < 0 ? null : urlOrPath.substring(index+1); } @NotNull public static List markDirty(boolean recursive, boolean reloadChildren, VirtualFile... files) { List list = ContainerUtil.filter(Condition.NOT_NULL, files); if (list.isEmpty()) { return Collections.emptyList(); } for (VirtualFile file : list) { if (reloadChildren) { file.getChildren(); } if (file instanceof NewVirtualFile) { if (recursive) { ((NewVirtualFile)file).markDirtyRecursively(); } else { ((NewVirtualFile)file).markDirty(); } } } return list; } public static void markDirtyAndRefresh(boolean async, boolean recursive, boolean reloadChildren, VirtualFile... files) { List list = markDirty(recursive, reloadChildren, files); if (list.isEmpty()) return; LocalFileSystem.getInstance().refreshFiles(list, async, recursive, null); } }