IDEADEV-41995
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / vfs / impl / local / LocalFileSystemBase.java
blobb0451ede171c8202a3b1b4bd710fcc208392583b
1 /*
2 * Copyright 2000-2009 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.impl.local;
18 import com.intellij.openapi.application.Application;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.util.SystemInfo;
22 import com.intellij.openapi.util.io.FileUtil;
23 import com.intellij.openapi.vfs.*;
24 import com.intellij.openapi.vfs.ex.VirtualFileManagerEx;
25 import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
26 import com.intellij.openapi.vfs.newvfs.RefreshQueue;
27 import com.intellij.openapi.vfs.newvfs.impl.FakeVirtualFile;
28 import com.intellij.util.ArrayUtil;
29 import com.intellij.util.Processor;
30 import com.intellij.util.ThrowableConsumer;
31 import com.intellij.util.io.SafeFileOutputStream;
32 import com.intellij.util.io.fs.IFile;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
36 import java.io.*;
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.Locale;
41 /**
42 * @author Dmitry Avdeev
44 public abstract class LocalFileSystemBase extends LocalFileSystem {
46 protected static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl");
47 private final List<LocalFileOperationsHandler> myHandlers = new ArrayList<LocalFileOperationsHandler>();
49 @Nullable
50 public VirtualFile findFileByPath(@NotNull String path) {
52 if (File.separatorChar == '\\') {
53 if (path.indexOf('\\') >= 0) return null;
57 String canonicalPath = getVfsCanonicalPath(path);
58 if (canonicalPath == null) return null;
59 return super.findFileByPath(canonicalPath);
62 public VirtualFile findFileByPathIfCached(@NotNull String path) {
63 String canonicalPath = getVfsCanonicalPath(path);
64 if (canonicalPath == null) return null;
65 return super.findFileByPathIfCached(canonicalPath);
68 @Nullable
69 public VirtualFile refreshAndFindFileByPath(@NotNull String path) {
70 String canonicalPath = getVfsCanonicalPath(path);
71 if (canonicalPath == null) return null;
72 return super.refreshAndFindFileByPath(canonicalPath);
75 public VirtualFile findFileByIoFile(File file) {
76 String path = file.getAbsolutePath();
77 if (path == null) return null;
78 return findFileByPath(path.replace(File.separatorChar, '/'));
81 @Nullable
82 public VirtualFile findFileByIoFile(final IFile file) {
83 String path = file.getPath();
84 if (path == null) return null;
85 return findFileByPath(path.replace(File.separatorChar, '/'));
88 @Nullable
89 protected static String getVfsCanonicalPath(@NotNull String path) {
90 if (path.length() == 0) {
91 try {
92 return new File("").getCanonicalPath();
94 catch (IOException e) {
95 return path;
99 if (SystemInfo.isWindows) {
100 if (path.startsWith("//") || path.startsWith("\\\\")) {
101 return path;
104 if (path.charAt(0) == '/') path = path.substring(1); //hack over new File(path).toUrl().getFile()
105 if (path.contains("~")) {
106 try {
107 return new File(path.replace('/', File.separatorChar)).getCanonicalPath().replace(File.separatorChar, '/');
109 catch (IOException e) {
110 return null;
114 else {
115 if (!path.startsWith("/")) {
116 path = new File(path).getAbsolutePath();
121 return path.replace(File.separatorChar, '/');
124 @SuppressWarnings({"NonConstantStringShouldBeStringBuffer"})
125 protected static File convertToIOFile(VirtualFile file) {
126 String path = file.getPath();
127 if (path.endsWith(":") && path.length() == 2 && (SystemInfo.isWindows || SystemInfo.isOS2)) {
128 path += "/"; // Make 'c:' resolve to a root directory for drive c:, not the current directory on that drive
131 return new File(path);
134 public boolean exists(final VirtualFile fileOrDirectory) {
135 if (fileOrDirectory.getParent() == null) return true;
136 return convertToIOFile(fileOrDirectory).exists();
139 public long getLength(final VirtualFile file) {
140 return convertToIOFile(file).length();
143 public long getTimeStamp(final VirtualFile file) {
144 return convertToIOFile(file).lastModified();
147 public boolean isDirectory(final VirtualFile file) {
148 return convertToIOFile(file).isDirectory();
151 public boolean isWritable(final VirtualFile file) {
152 return convertToIOFile(file).canWrite();
155 public String[] list(final VirtualFile file) {
156 if (file.getParent() == null) {
157 final File[] roots = File.listRoots();
158 if (roots.length == 1 && roots[0].getName().length() == 0) {
159 return roots[0].list();
161 if ("".equals(file.getName())) {
162 // return drive letter names for the 'fake' root on windows
163 final String[] names = new String[roots.length];
164 for (int i = 0; i < names.length; i++) {
165 String name = roots[i].getPath();
166 if (name.endsWith(File.separator)) {
167 name = name.substring(0, name.length() - File.separator.length());
169 names[i] = name;
171 return names;
174 final String[] names = convertToIOFile(file).list();
175 return names != null ? names : ArrayUtil.EMPTY_STRING_ARRAY;
178 @NotNull
179 public String getProtocol() {
180 return PROTOCOL;
183 @Nullable
184 public String normalize(final String path) {
185 return getVfsCanonicalPath(path);
188 public VirtualFile refreshAndFindFileByIoFile(@NotNull File file) {
189 String path = file.getAbsolutePath();
190 if (path == null) return null;
191 return refreshAndFindFileByPath(path.replace(File.separatorChar, '/'));
194 @Nullable
195 public VirtualFile refreshAndFindFileByIoFile(final IFile ioFile) {
196 String path = ioFile.getPath();
197 if (path == null) return null;
198 return refreshAndFindFileByPath(path.replace(File.separatorChar, '/'));
201 public void refreshIoFiles(Iterable<File> files) {
202 final VirtualFileManagerEx manager = (VirtualFileManagerEx)VirtualFileManager.getInstance();
204 Application app = ApplicationManager.getApplication();
205 boolean fireCommonRefreshSession = app.isDispatchThread() || app.isWriteAccessAllowed();
206 if (fireCommonRefreshSession) manager.fireBeforeRefreshStart(false);
208 try {
209 List<VirtualFile> filesToRefresh = new ArrayList<VirtualFile>();
211 for (File file : files) {
212 final VirtualFile virtualFile = refreshAndFindFileByIoFile(file);
213 if (virtualFile != null) {
214 filesToRefresh.add(virtualFile);
218 RefreshQueue.getInstance().refresh(false, false, null, VfsUtil.toVirtualFileArray(filesToRefresh));
220 finally {
221 if (fireCommonRefreshSession) manager.fireAfterRefreshFinish(false);
225 public void refreshFiles(Iterable<VirtualFile> files) {
226 refreshFiles(files, false, false);
229 protected static void refreshFiles(final Iterable<VirtualFile> files, final boolean recursive, final boolean async) {
230 List<VirtualFile> list = new ArrayList<VirtualFile>();
231 for (VirtualFile file : files) {
232 list.add(file);
235 RefreshQueue.getInstance().refresh(async, recursive, null, VfsUtil.toVirtualFileArray(list));
238 public byte[] physicalContentsToByteArray(final VirtualFile virtualFile) throws IOException {
239 return virtualFile.contentsToByteArray();
242 public long physicalLength(final VirtualFile virtualFile) {
243 return virtualFile.getLength();
246 public void registerAuxiliaryFileOperationsHandler(LocalFileOperationsHandler handler) {
247 if (myHandlers.contains(handler)) {
248 LOG.error("Handler " + handler + " already registered.");
250 myHandlers.add(handler);
253 public void unregisterAuxiliaryFileOperationsHandler(LocalFileOperationsHandler handler) {
254 if (!myHandlers.remove(handler)) {
255 LOG.error("Handler" + handler + " haven't been registered or already unregistered.");
259 public boolean processCachedFilesInSubtree(final VirtualFile file, Processor<VirtualFile> processor) {
260 if (file.getFileSystem() != this) return true;
262 return processFile((NewVirtualFile)file, processor);
265 private static boolean processFile(NewVirtualFile file, Processor<VirtualFile> processor) {
266 if (!processor.process(file)) return false;
267 if (file.isDirectory()) {
268 for (final VirtualFile child : file.getCachedChildren()) {
269 if (!processFile((NewVirtualFile)child, processor)) return false;
272 return true;
275 private boolean auxDelete(VirtualFile file) throws IOException {
276 for (LocalFileOperationsHandler handler : myHandlers) {
277 if (handler.delete(file)) return true;
280 return false;
283 private boolean auxMove(VirtualFile file, VirtualFile toDir) throws IOException {
284 for (LocalFileOperationsHandler handler : myHandlers) {
285 if (handler.move(file, toDir)) return true;
287 return false;
290 private void auxNotifyCompleted(final ThrowableConsumer<LocalFileOperationsHandler, IOException> consumer) {
291 for (LocalFileOperationsHandler handler : myHandlers) {
292 handler.afterDone(consumer);
296 @Nullable
297 private File auxCopy(VirtualFile file, VirtualFile toDir, final String copyName) throws IOException {
298 for (LocalFileOperationsHandler handler : myHandlers) {
299 final File copy = handler.copy(file, toDir, copyName);
300 if (copy != null) return copy;
302 return null;
305 private boolean auxRename(VirtualFile file, String newName) throws IOException {
306 for (LocalFileOperationsHandler handler : myHandlers) {
307 if (handler.rename(file, newName)) return true;
309 return false;
312 private boolean auxCreateFile(VirtualFile dir, String name) throws IOException {
313 for (LocalFileOperationsHandler handler : myHandlers) {
314 if (handler.createFile(dir, name)) return true;
316 return false;
319 private boolean auxCreateDirectory(VirtualFile dir, String name) throws IOException {
320 for (LocalFileOperationsHandler handler : myHandlers) {
321 if (handler.createDirectory(dir, name)) return true;
323 return false;
326 private static void delete(File physicalFile) throws IOException {
327 File[] list = physicalFile.listFiles();
328 if (list != null) {
329 for (File aList : list) {
330 delete(aList);
333 if (!physicalFile.delete()) {
334 throw new IOException(VfsBundle.message("file.delete.error", physicalFile.getPath()));
338 public VirtualFile createChildDirectory(final Object requestor, @NotNull final VirtualFile parent, @NotNull final String dir) throws IOException {
339 final File ioDir = new File(convertToIOFile(parent), dir);
340 final boolean succ = auxCreateDirectory(parent, dir) || ioDir.mkdirs();
341 auxNotifyCompleted(new ThrowableConsumer<LocalFileOperationsHandler, IOException>() {
342 public void consume(LocalFileOperationsHandler handler) throws IOException {
343 handler.createDirectory(parent, dir);
346 if (!succ) {
347 throw new IOException("Failed to create directory: " + ioDir.getPath());
350 return new FakeVirtualFile(parent, dir);
353 public VirtualFile createChildFile(final Object requestor, @NotNull final VirtualFile parent, @NotNull final String file) throws IOException {
354 final File ioFile = new File(convertToIOFile(parent), file);
355 final boolean succ = auxCreateFile(parent, file) || FileUtil.createIfDoesntExist(ioFile);
356 auxNotifyCompleted(new ThrowableConsumer<LocalFileOperationsHandler, IOException>() {
357 public void consume(LocalFileOperationsHandler handler) throws IOException {
358 handler.createFile(parent, file);
361 if (!succ) {
362 throw new IOException("Failed to create child file at " + ioFile.getPath());
365 return new FakeVirtualFile(parent, file);
368 public void deleteFile(final Object requestor, @NotNull final VirtualFile file) throws IOException {
369 if (!auxDelete(file)) {
370 delete(convertToIOFile(file));
372 auxNotifyCompleted(new ThrowableConsumer<LocalFileOperationsHandler, IOException>() {
373 public void consume(LocalFileOperationsHandler handler) throws IOException {
374 handler.delete(file);
379 public boolean isCaseSensitive() {
380 return SystemInfo.isFileSystemCaseSensitive;
383 @NotNull
384 public InputStream getInputStream(final VirtualFile file) throws FileNotFoundException {
385 return new BufferedInputStream(new FileInputStream(convertToIOFile(file)));
388 @NotNull
389 public byte[] contentsToByteArray(final VirtualFile file) throws IOException {
390 return FileUtil.loadFileBytes(convertToIOFile(file));
393 @NotNull
394 public OutputStream getOutputStream(final VirtualFile file, final Object requestor, final long modStamp, final long timeStamp) throws FileNotFoundException {
395 final File ioFile = convertToIOFile(file);
396 final OutputStream stream = shallUseSafeStream(requestor, ioFile) ? new SafeFileOutputStream(ioFile) : new FileOutputStream(ioFile);
397 return new BufferedOutputStream(stream) {
398 public void close() throws IOException {
399 super.close();
400 if (timeStamp > 0) {
401 ioFile.setLastModified(timeStamp);
407 private static boolean shallUseSafeStream(Object requestor, File file) {
408 return requestor instanceof SafeWriteRequestor && FileUtil.canCallCanExecute() && !FileUtil.canExecute(file);
411 public void moveFile(final Object requestor, @NotNull final VirtualFile file, @NotNull final VirtualFile newParent) throws IOException {
412 if (!auxMove(file, newParent)) {
413 final File ioFrom = convertToIOFile(file);
414 final File ioParent = convertToIOFile(newParent);
415 ioFrom.renameTo(new File(ioParent, file.getName()));
417 auxNotifyCompleted(new ThrowableConsumer<LocalFileOperationsHandler, IOException>() {
418 public void consume(LocalFileOperationsHandler handler) throws IOException {
419 handler.move(file, newParent);
424 public void renameFile(final Object requestor, @NotNull final VirtualFile file, @NotNull final String newName) throws IOException {
425 if (!file.exists()) {
426 throw new IOException("File to move does not exist: " + file.getPath());
429 final VirtualFile parent = file.getParent();
430 assert parent != null;
432 if (!auxRename(file, newName)) {
433 if (!convertToIOFile(file).renameTo(new File(convertToIOFile(parent), newName))) {
434 throw new IOException("Destination already exists: " + parent.getPath() + "/" + newName);
437 auxNotifyCompleted(new ThrowableConsumer<LocalFileOperationsHandler, IOException>() {
438 public void consume(LocalFileOperationsHandler handler) throws IOException {
439 handler.rename(file, newName);
444 public VirtualFile copyFile(final Object requestor, @NotNull final VirtualFile vFile, @NotNull final VirtualFile newParent, @NotNull final String copyName)
445 throws IOException {
446 File physicalCopy = auxCopy(vFile, newParent, copyName);
448 try {
449 if (physicalCopy == null) {
450 File physicalFile = convertToIOFile(vFile);
452 File newPhysicalParent = convertToIOFile(newParent);
453 physicalCopy = new File(newPhysicalParent, copyName);
455 try {
456 if (physicalFile.isDirectory()) {
457 FileUtil.copyDir(physicalFile, physicalCopy);
459 else {
460 FileUtil.copy(physicalFile, physicalCopy);
463 catch (IOException e) {
464 FileUtil.delete(physicalCopy);
465 throw e;
468 } finally {
469 auxNotifyCompleted(new ThrowableConsumer<LocalFileOperationsHandler, IOException>() {
470 public void consume(LocalFileOperationsHandler handler) throws IOException {
471 handler.copy(vFile, newParent, copyName);
475 return new FakeVirtualFile(newParent, copyName);
478 public void setTimeStamp(final VirtualFile file, final long modstamp) {
479 convertToIOFile(file).setLastModified(modstamp);
482 public void setWritable(final VirtualFile file, final boolean writableFlag) throws IOException {
483 FileUtil.setReadOnlyAttribute(file.getPath(), !writableFlag);
484 final File ioFile = convertToIOFile(file);
485 if (ioFile.canWrite() != writableFlag) {
486 throw new IOException("Failed to change read-only flag for " + ioFile.getPath());
490 protected String extractRootPath(@NotNull final String path) {
491 if (path.length() == 0) {
492 try {
493 return extractRootPath(new File("").getCanonicalPath());
495 catch (IOException e) {
496 throw new RuntimeException(e);
500 if (SystemInfo.isWindows) {
501 if (path.length() >= 2 && path.charAt(1) == ':') {
502 // Drive letter
503 return path.substring(0, 2).toUpperCase(Locale.US);
506 if (path.startsWith("//") || path.startsWith("\\\\")) {
507 // UNC. Must skip exactly two path elements like [\\ServerName\ShareName]\pathOnShare\file.txt
508 // Root path is in square brackets here.
510 int slashCount = 0;
511 int idx;
512 for (idx = 2; idx < path.length() && slashCount < 2; idx++) {
513 final char c = path.charAt(idx);
514 if (c == '\\' || c == '/') {
515 slashCount++;
516 idx--;
520 return path.substring(0, idx);
523 return "";
526 return path.startsWith("/") ? "/" : "";
529 public int getRank() {
530 return 1;
533 public boolean markNewFilesAsDirty() {
534 return true;
537 public String getCanonicallyCasedName(final VirtualFile file) {
538 if (isCaseSensitive()) {
539 return super.getCanonicallyCasedName(file);
542 try {
543 return convertToIOFile(file).getCanonicalFile().getName();
545 catch (IOException e) {
546 return file.getName();