| /* |
| * 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"); |
| |
| /** @deprecated incorrect name, use {@link #VFS_SEPARATOR_CHAR} (to be removed in IDEA 15) */ |
| public static final char VFS_PATH_SEPARATOR = VFS_SEPARATOR_CHAR; |
| |
| 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 <code>filter</code> from <code>fromDir</code> to <code>toDir</code>. |
| * 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 <code>requestor</code> is <code>null</code>. |
| * 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 <code>file</code> in the <code>toDir</code> 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 <code>requestor</code> is <code>null</code>. |
| * 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<VirtualFile,Set<VirtualFile>> map = new HashMap<VirtualFile, Set<VirtualFile>>(); |
| for (VirtualFile aFile : files) { |
| VirtualFile directory = aFile.isDirectory() ? aFile : aFile.getParent(); |
| if (directory == null) return VirtualFile.EMPTY_ARRAY; |
| VirtualFile[] path = getPathComponents(directory); |
| Set<VirtualFile> filesSet; |
| final VirtualFile firstPart = path[0]; |
| if (map.containsKey(firstPart)) { |
| filesSet = map.get(firstPart); |
| } |
| else { |
| filesSet = new THashSet<VirtualFile>(); |
| map.put(firstPart, filesSet); |
| } |
| filesSet.add(directory); |
| } |
| // Find common ancestor for each set of files. |
| ArrayList<VirtualFile> ancestorsList = new ArrayList<VirtualFile>(); |
| for (Set<VirtualFile> 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<? extends VirtualFile> 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 <code>{@link VirtualFile}</code> if the file was found, <code>null</code> 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<VirtualFile>() { |
| @Override |
| protected void run(Result<VirtualFile> 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<VirtualFile> collectChildrenRecursively(@NotNull final VirtualFile root) { |
| final List<VirtualFile> result = new ArrayList<VirtualFile>(); |
| processFilesRecursively(root, new Processor<VirtualFile>() { |
| @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<VirtualFile> processor) { |
| final FileTypeManager ftm = FileTypeManager.getInstance(); |
| processFilesRecursively(root, processor, new Convertor<VirtualFile, Boolean>() { |
| public Boolean convert(final VirtualFile vf) { |
| return ! ftm.isFileIgnored(vf); |
| } |
| }); |
| } |
| |
| @Nullable |
| public static <T> T processInputStream(@NotNull final VirtualFile file, @NotNull Function<InputStream, T> 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(VfsUtil.VFS_SEPARATOR_CHAR); |
| 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(VfsUtil.VFS_SEPARATOR_CHAR); |
| return index < 0 ? null : urlOrPath.substring(index+1); |
| } |
| |
| @NotNull |
| public static List<VirtualFile> markDirty(boolean recursive, boolean reloadChildren, @NotNull VirtualFile... files) { |
| List<VirtualFile> 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, @NotNull VirtualFile... files) { |
| List<VirtualFile> list = markDirty(recursive, reloadChildren, files); |
| if (list.isEmpty()) return; |
| LocalFileSystem.getInstance().refreshFiles(list, async, recursive, null); |
| } |
| } |