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
;
37 import java
.util
.ArrayList
;
38 import java
.util
.List
;
39 import java
.util
.Locale
;
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
>();
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
);
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
, '/'));
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
, '/'));
89 protected static String
getVfsCanonicalPath(@NotNull String path
) {
90 if (path
.length() == 0) {
92 return new File("").getCanonicalPath();
94 catch (IOException e
) {
99 if (SystemInfo
.isWindows
) {
100 if (path
.startsWith("//") || path
.startsWith("\\\\")) {
104 if (path
.charAt(0) == '/') path
= path
.substring(1); //hack over new File(path).toUrl().getFile()
105 if (path
.contains("~")) {
107 return new File(path
.replace('/', File
.separatorChar
)).getCanonicalPath().replace(File
.separatorChar
, '/');
109 catch (IOException e
) {
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());
174 final String
[] names
= convertToIOFile(file
).list();
175 return names
!= null ? names
: ArrayUtil
.EMPTY_STRING_ARRAY
;
179 public String
getProtocol() {
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
, '/'));
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);
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
));
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
) {
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;
275 private boolean auxDelete(VirtualFile file
) throws IOException
{
276 for (LocalFileOperationsHandler handler
: myHandlers
) {
277 if (handler
.delete(file
)) return true;
283 private boolean auxMove(VirtualFile file
, VirtualFile toDir
) throws IOException
{
284 for (LocalFileOperationsHandler handler
: myHandlers
) {
285 if (handler
.move(file
, toDir
)) return true;
290 private void auxNotifyCompleted(final ThrowableConsumer
<LocalFileOperationsHandler
, IOException
> consumer
) {
291 for (LocalFileOperationsHandler handler
: myHandlers
) {
292 handler
.afterDone(consumer
);
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
;
305 private boolean auxRename(VirtualFile file
, String newName
) throws IOException
{
306 for (LocalFileOperationsHandler handler
: myHandlers
) {
307 if (handler
.rename(file
, newName
)) return true;
312 private boolean auxCreateFile(VirtualFile dir
, String name
) throws IOException
{
313 for (LocalFileOperationsHandler handler
: myHandlers
) {
314 if (handler
.createFile(dir
, name
)) return true;
319 private boolean auxCreateDirectory(VirtualFile dir
, String name
) throws IOException
{
320 for (LocalFileOperationsHandler handler
: myHandlers
) {
321 if (handler
.createDirectory(dir
, name
)) return true;
326 private static void delete(File physicalFile
) throws IOException
{
327 File
[] list
= physicalFile
.listFiles();
329 for (File aList
: list
) {
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
);
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
);
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
;
384 public InputStream
getInputStream(final VirtualFile file
) throws FileNotFoundException
{
385 return new BufferedInputStream(new FileInputStream(convertToIOFile(file
)));
389 public byte[] contentsToByteArray(final VirtualFile file
) throws IOException
{
390 return FileUtil
.loadFileBytes(convertToIOFile(file
));
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
{
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
)
446 File physicalCopy
= auxCopy(vFile
, newParent
, copyName
);
449 if (physicalCopy
== null) {
450 File physicalFile
= convertToIOFile(vFile
);
452 File newPhysicalParent
= convertToIOFile(newParent
);
453 physicalCopy
= new File(newPhysicalParent
, copyName
);
456 if (physicalFile
.isDirectory()) {
457 FileUtil
.copyDir(physicalFile
, physicalCopy
);
460 FileUtil
.copy(physicalFile
, physicalCopy
);
463 catch (IOException e
) {
464 FileUtil
.delete(physicalCopy
);
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) {
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) == ':') {
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.
512 for (idx
= 2; idx
< path
.length() && slashCount
< 2; idx
++) {
513 final char c
= path
.charAt(idx
);
514 if (c
== '\\' || c
== '/') {
520 return path
.substring(0, idx
);
526 return path
.startsWith("/") ?
"/" : "";
529 public int getRank() {
533 public boolean markNewFilesAsDirty() {
537 public String
getCanonicallyCasedName(final VirtualFile file
) {
538 if (isCaseSensitive()) {
539 return super.getCanonicallyCasedName(file
);
543 return convertToIOFile(file
).getCanonicalFile().getName();
545 catch (IOException e
) {
546 return file
.getName();