b1c3d90a8aedddf319111dbf31dce9a656e7bcd1
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / vfs / newvfs / persistent / PersistentFSImpl.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.openapi.vfs.newvfs.persistent;
17
18 import com.intellij.openapi.application.ApplicationManager;
19 import com.intellij.openapi.application.ex.ApplicationEx;
20 import com.intellij.openapi.components.ApplicationComponent;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.fileTypes.FileType;
23 import com.intellij.openapi.fileTypes.FileTypeRegistry;
24 import com.intellij.openapi.fileTypes.FileTypes;
25 import com.intellij.openapi.util.LowMemoryWatcher;
26 import com.intellij.openapi.util.ShutDownTracker;
27 import com.intellij.openapi.util.SystemInfo;
28 import com.intellij.openapi.util.io.*;
29 import com.intellij.openapi.vfs.*;
30 import com.intellij.openapi.vfs.ex.temp.TempFileSystem;
31 import com.intellij.openapi.vfs.newvfs.*;
32 import com.intellij.openapi.vfs.newvfs.events.*;
33 import com.intellij.openapi.vfs.newvfs.impl.*;
34 import com.intellij.util.*;
35 import com.intellij.util.containers.ConcurrentIntObjectMap;
36 import com.intellij.util.containers.ContainerUtil;
37 import com.intellij.util.containers.EmptyIntHashSet;
38 import com.intellij.util.io.ReplicatorInputStream;
39 import com.intellij.util.io.URLUtil;
40 import com.intellij.util.messages.MessageBus;
41 import gnu.trove.*;
42 import org.jetbrains.annotations.NonNls;
43 import org.jetbrains.annotations.NotNull;
44 import org.jetbrains.annotations.Nullable;
45 import org.jetbrains.annotations.TestOnly;
46
47 import java.io.*;
48 import java.util.*;
49 import java.util.concurrent.atomic.AtomicBoolean;
50 import java.util.concurrent.locks.ReadWriteLock;
51 import java.util.concurrent.locks.ReentrantReadWriteLock;
52
53 /**
54  * @author max
55  */
56 public class PersistentFSImpl extends PersistentFS implements ApplicationComponent {
57   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.newvfs.persistent.PersistentFS");
58
59   private final MessageBus myEventBus;
60
61   private final ReadWriteLock myRootsLock = new ReentrantReadWriteLock();
62   private final Map<String, VirtualFileSystemEntry> myRoots = ContainerUtil.newTroveMap(FileUtil.PATH_HASHING_STRATEGY);
63   private final TIntObjectHashMap<VirtualFileSystemEntry> myRootsById = new TIntObjectHashMap<VirtualFileSystemEntry>();
64
65   private final ConcurrentIntObjectMap<VirtualFileSystemEntry> myIdToDirCache = ContainerUtil.createConcurrentIntObjectMap();
66   private final Object myInputLock = new Object();
67
68   private final AtomicBoolean myShutDown = new AtomicBoolean(false);
69   @SuppressWarnings("FieldCanBeLocal")
70   private final LowMemoryWatcher myWatcher = LowMemoryWatcher.register(new Runnable() {
71     @Override
72     public void run() {
73       clearIdCache();
74     }
75   });
76
77   public PersistentFSImpl(@NotNull MessageBus bus) {
78     myEventBus = bus;
79     ShutDownTracker.getInstance().registerShutdownTask(new Runnable() {
80       @Override
81       public void run() {
82         performShutdown();
83       }
84     });
85   }
86
87   @Override
88   public void initComponent() {
89     FSRecords.connect();
90   }
91
92   @Override
93   public void disposeComponent() {
94     performShutdown();
95   }
96
97   private void performShutdown() {
98     if (myShutDown.compareAndSet(false, true)) {
99       LOG.info("VFS dispose started");
100       FSRecords.dispose();
101       LOG.info("VFS dispose completed");
102     }
103   }
104
105   @Override
106   @NonNls
107   @NotNull
108   public String getComponentName() {
109     return "app.component.PersistentFS";
110   }
111
112   @Override
113   public boolean areChildrenLoaded(@NotNull final VirtualFile dir) {
114     return areChildrenLoaded(getFileId(dir));
115   }
116
117   @Override
118   public long getCreationTimestamp() {
119     return FSRecords.getCreationTimestamp();
120   }
121
122   @NotNull
123   private static NewVirtualFileSystem getDelegate(@NotNull VirtualFile file) {
124     return (NewVirtualFileSystem)file.getFileSystem();
125   }
126
127   @Override
128   public boolean wereChildrenAccessed(@NotNull final VirtualFile dir) {
129     return FSRecords.wereChildrenAccessed(getFileId(dir));
130   }
131
132   @Override
133   @NotNull
134   public String[] list(@NotNull final VirtualFile file) {
135     int id = getFileId(file);
136
137     FSRecords.NameId[] nameIds = FSRecords.listAll(id);
138     if (!areChildrenLoaded(id)) {
139       nameIds  = persistAllChildren(file, id, nameIds);
140     }
141     return ContainerUtil.map2Array(nameIds, String.class, new Function<FSRecords.NameId, String>() {
142       @Override
143       public String fun(FSRecords.NameId id) {
144         return id.name.toString();
145       }
146     });
147   }
148
149   @Override
150   @NotNull
151   public String[] listPersisted(@NotNull VirtualFile parent) {
152     return listPersisted(FSRecords.list(getFileId(parent)));
153   }
154
155   @NotNull
156   private static String[] listPersisted(@NotNull int[] childrenIds) {
157     String[] names = ArrayUtil.newStringArray(childrenIds.length);
158     for (int i = 0; i < childrenIds.length; i++) {
159       names[i] = FSRecords.getName(childrenIds[i]);
160     }
161     return names;
162   }
163
164   @NotNull
165   private static FSRecords.NameId[] persistAllChildren(@NotNull final VirtualFile file, final int id, @NotNull FSRecords.NameId[] current) {
166     final NewVirtualFileSystem fs = replaceWithNativeFS(getDelegate(file));
167
168     String[] delegateNames = VfsUtil.filterNames(fs.list(file));
169     if (delegateNames.length == 0 && current.length > 0) {
170       return current;
171     }
172
173     Set<String> toAdd = ContainerUtil.newHashSet(delegateNames);
174     for (FSRecords.NameId nameId : current) {
175       toAdd.remove(nameId.name.toString());
176     }
177
178     final TIntArrayList childrenIds = new TIntArrayList(current.length + toAdd.size());
179     final List<FSRecords.NameId> nameIds = ContainerUtil.newArrayListWithCapacity(current.length + toAdd.size());
180     for (FSRecords.NameId nameId : current) {
181       childrenIds.add(nameId.id);
182       nameIds.add(nameId);
183     }
184     for (String newName : toAdd) {
185       FakeVirtualFile child = new FakeVirtualFile(file, newName);
186       FileAttributes attributes = fs.getAttributes(child);
187       if (attributes != null) {
188         int childId = createAndFillRecord(fs, child, id, attributes);
189         childrenIds.add(childId);
190         nameIds.add(new FSRecords.NameId(childId, FileNameCache.storeName(newName), newName));
191       }
192     }
193
194     FSRecords.updateList(id, childrenIds.toNativeArray());
195     setChildrenCached(id);
196
197     return nameIds.toArray(new FSRecords.NameId[nameIds.size()]);
198   }
199
200   public static void setChildrenCached(int id) {
201     int flags = FSRecords.getFlags(id);
202     FSRecords.setFlags(id, flags | CHILDREN_CACHED_FLAG, true);
203   }
204
205   @Override
206   @NotNull
207   public FSRecords.NameId[] listAll(@NotNull VirtualFile parent) {
208     final int parentId = getFileId(parent);
209
210     FSRecords.NameId[] nameIds = FSRecords.listAll(parentId);
211     if (!areChildrenLoaded(parentId)) {
212       return persistAllChildren(parent, parentId, nameIds);
213     }
214
215     return nameIds;
216   }
217
218   private static boolean areChildrenLoaded(final int parentId) {
219     return (FSRecords.getFlags(parentId) & CHILDREN_CACHED_FLAG) != 0;
220   }
221
222   @Override
223   @Nullable
224   public DataInputStream readAttribute(@NotNull final VirtualFile file, @NotNull final FileAttribute att) {
225     return FSRecords.readAttributeWithLock(getFileId(file), att);
226   }
227
228   @Override
229   @NotNull
230   public DataOutputStream writeAttribute(@NotNull final VirtualFile file, @NotNull final FileAttribute att) {
231     return FSRecords.writeAttribute(getFileId(file), att);
232   }
233
234   @Nullable
235   private static DataInputStream readContent(@NotNull VirtualFile file) {
236     return FSRecords.readContent(getFileId(file));
237   }
238
239   @Nullable
240   private static DataInputStream readContentById(int contentId) {
241     return FSRecords.readContentById(contentId);
242   }
243
244   @NotNull
245   private static DataOutputStream writeContent(@NotNull VirtualFile file, boolean readOnly) {
246     return FSRecords.writeContent(getFileId(file), readOnly);
247   }
248
249   private static void writeContent(@NotNull VirtualFile file, ByteSequence content, boolean readOnly) throws IOException {
250     FSRecords.writeContent(getFileId(file), content, readOnly);
251   }
252
253   @Override
254   public int storeUnlinkedContent(@NotNull byte[] bytes) {
255     return FSRecords.storeUnlinkedContent(bytes);
256   }
257
258   @Override
259   public int getModificationCount(@NotNull final VirtualFile file) {
260     return FSRecords.getModCount(getFileId(file));
261   }
262
263   @Override
264   public int getCheapFileSystemModificationCount() {
265     return FSRecords.getLocalModCount();
266   }
267
268   @Override
269   public int getFilesystemModificationCount() {
270     return FSRecords.getModCount();
271   }
272
273   private static boolean writeAttributesToRecord(final int id,
274                                                  final int parentId,
275                                                  @NotNull VirtualFile file,
276                                                  @NotNull NewVirtualFileSystem fs,
277                                                  @NotNull FileAttributes attributes) {
278     String name = file.getName();
279     if (!name.isEmpty()) {
280       if (namesEqual(fs, name, FSRecords.getName(id))) return false; // TODO: Handle root attributes change.
281     }
282     else {
283       if (areChildrenLoaded(id)) return false; // TODO: hack
284     }
285
286     FSRecords.writeAttributesToRecord(id, parentId, attributes, name);
287
288     return true;
289   }
290
291   @Override
292   public int getFileAttributes(int id) {
293     assert id > 0;
294     //noinspection MagicConstant
295     return FSRecords.getFlags(id);
296   }
297
298   @Override
299   public boolean isDirectory(@NotNull final VirtualFile file) {
300     return isDirectory(getFileAttributes(getFileId(file)));
301   }
302
303   private static int getParent(final int id) {
304     assert id > 0;
305     return FSRecords.getParent(id);
306   }
307
308   private static boolean namesEqual(@NotNull VirtualFileSystem fs, @NotNull String n1, String n2) {
309     return fs.isCaseSensitive() ? n1.equals(n2) : n1.equalsIgnoreCase(n2);
310   }
311
312   @Override
313   public boolean exists(@NotNull final VirtualFile fileOrDirectory) {
314     return ((VirtualFileWithId)fileOrDirectory).getId() > 0;
315   }
316
317   @Override
318   public long getTimeStamp(@NotNull final VirtualFile file) {
319     return FSRecords.getTimestamp(getFileId(file));
320   }
321
322   @Override
323   public void setTimeStamp(@NotNull final VirtualFile file, final long modStamp) throws IOException {
324     final int id = getFileId(file);
325     FSRecords.setTimestamp(id, modStamp);
326     getDelegate(file).setTimeStamp(file, modStamp);
327   }
328
329   private static int getFileId(@NotNull VirtualFile file) {
330     final int id = ((VirtualFileWithId)file).getId();
331     if (id <= 0) {
332       throw new InvalidVirtualFileAccessException(file);
333     }
334     return id;
335   }
336
337   @Override
338   public boolean isSymLink(@NotNull VirtualFile file) {
339     return isSymLink(getFileAttributes(getFileId(file)));
340   }
341
342   @Override
343   public String resolveSymLink(@NotNull VirtualFile file) {
344     throw new UnsupportedOperationException();
345   }
346
347   @Override
348   public boolean isSpecialFile(@NotNull VirtualFile file) {
349     return isSpecialFile(getFileAttributes(getFileId(file)));
350   }
351
352   @Override
353   public boolean isWritable(@NotNull VirtualFile file) {
354     return (getFileAttributes(getFileId(file)) & IS_READ_ONLY) == 0;
355   }
356
357   @Override
358   public boolean isHidden(@NotNull VirtualFile file) {
359     return (getFileAttributes(getFileId(file)) & IS_HIDDEN) != 0;
360   }
361
362   @Override
363   public void setWritable(@NotNull final VirtualFile file, final boolean writableFlag) throws IOException {
364     getDelegate(file).setWritable(file, writableFlag);
365     boolean oldWritable = isWritable(file);
366     if (oldWritable != writableFlag) {
367       processEvent(new VFilePropertyChangeEvent(this, file, VirtualFile.PROP_WRITABLE, oldWritable, writableFlag, false));
368     }
369   }
370
371   @Override
372   public int getId(@NotNull VirtualFile parent, @NotNull String childName, @NotNull NewVirtualFileSystem fs) {
373     int parentId = getFileId(parent);
374     int[] children = FSRecords.list(parentId);
375
376     if (children.length > 0) {
377       // fast path, check that some child has same nameId as given name, this avoid O(N) on retrieving names for processing non-cached children
378       int nameId = FSRecords.getNameId(childName);
379       for (final int childId : children) {
380         if (nameId == FSRecords.getNameId(childId)) {
381           return childId;
382         }
383       }
384       // for case sensitive system the above check is exhaustive in consistent state of vfs
385     }
386
387     for (final int childId : children) {
388       if (namesEqual(fs, childName, FSRecords.getName(childId))) return childId;
389     }
390
391     final VirtualFile fake = new FakeVirtualFile(parent, childName);
392     final FileAttributes attributes = fs.getAttributes(fake);
393     if (attributes != null) {
394       final int child = createAndFillRecord(fs, fake, parentId, attributes);
395       FSRecords.updateList(parentId, ArrayUtil.append(children, child));
396       return child;
397     }
398
399     return 0;
400   }
401
402   @Override
403   public long getLength(@NotNull final VirtualFile file) {
404     long len;
405     if (mustReloadContent(file)) {
406       len = reloadLengthFromDelegate(file, getDelegate(file));
407     }
408     else {
409       final int id = getFileId(file);
410       len = FSRecords.getLength(id);
411     }
412
413     return len;
414   }
415
416   @NotNull
417   @Override
418   public VirtualFile copyFile(Object requestor, @NotNull VirtualFile file, @NotNull VirtualFile parent, @NotNull String name) throws IOException {
419     getDelegate(file).copyFile(requestor, file, parent, name);
420     processEvent(new VFileCopyEvent(requestor, file, parent, name));
421
422     final VirtualFile child = parent.findChild(name);
423     if (child == null) {
424       throw new IOException("Cannot create child");
425     }
426     return child;
427   }
428
429   @NotNull
430   @Override
431   public VirtualFile createChildDirectory(Object requestor, @NotNull VirtualFile parent, @NotNull String dir) throws IOException {
432     getDelegate(parent).createChildDirectory(requestor, parent, dir);
433     processEvent(new VFileCreateEvent(requestor, parent, dir, true, false));
434
435     final VirtualFile child = parent.findChild(dir);
436     if (child == null) {
437       throw new IOException("Cannot create child directory '" + dir + "' at " + parent.getPath());
438     }
439     return child;
440   }
441
442   @NotNull
443   @Override
444   public VirtualFile createChildFile(Object requestor, @NotNull VirtualFile parent, @NotNull String file) throws IOException {
445     getDelegate(parent).createChildFile(requestor, parent, file);
446     processEvent(new VFileCreateEvent(requestor, parent, file, false, false));
447
448     final VirtualFile child = parent.findChild(file);
449     if (child == null) {
450       throw new IOException("Cannot create child file '" + file + "' at " + parent.getPath());
451     }
452     return child;
453   }
454
455   @Override
456   public void deleteFile(final Object requestor, @NotNull final VirtualFile file) throws IOException {
457     final NewVirtualFileSystem delegate = getDelegate(file);
458     delegate.deleteFile(requestor, file);
459
460     if (!delegate.exists(file)) {
461       processEvent(new VFileDeleteEvent(requestor, file, false));
462     }
463   }
464
465   @Override
466   public void renameFile(final Object requestor, @NotNull VirtualFile file, @NotNull String newName) throws IOException {
467     getDelegate(file).renameFile(requestor, file, newName);
468     String oldName = file.getName();
469     if (!newName.equals(oldName)) {
470       processEvent(new VFilePropertyChangeEvent(requestor, file, VirtualFile.PROP_NAME, oldName, newName, false));
471     }
472   }
473
474   @Override
475   @NotNull
476   public byte[] contentsToByteArray(@NotNull final VirtualFile file) throws IOException {
477     return contentsToByteArray(file, true);
478   }
479
480   @Override
481   @NotNull
482   public byte[] contentsToByteArray(@NotNull final VirtualFile file, boolean cacheContent) throws IOException {
483     InputStream contentStream = null;
484     boolean reloadFromDelegate;
485     boolean outdated;
486     int fileId;
487     synchronized (myInputLock) {
488       fileId = getFileId(file);
489       outdated = checkFlag(fileId, MUST_RELOAD_CONTENT) || FSRecords.getLength(fileId) == -1L;
490       reloadFromDelegate = outdated || (contentStream = readContent(file)) == null;
491     }
492
493     if (reloadFromDelegate) {
494       final NewVirtualFileSystem delegate = getDelegate(file);
495
496       final byte[] content;
497       if (outdated) {
498         // in this case, file can have out-of-date length. so, update it first (it's needed for correct contentsToByteArray() work)
499         // see IDEA-90813 for possible bugs
500         FSRecords.setLength(fileId, delegate.getLength(file));
501         content = delegate.contentsToByteArray(file);
502       }
503       else {
504         // a bit of optimization
505         content = delegate.contentsToByteArray(file);
506         FSRecords.setLength(fileId, content.length);
507       }
508
509       ApplicationEx application = (ApplicationEx)ApplicationManager.getApplication();
510       // we should cache every local files content
511       // because the local history feature is currently depends on this cache,
512       // perforce offline mode as well
513       if ((!delegate.isReadOnly() ||
514            // do not cache archive content unless asked
515            cacheContent && !application.isInternal() && !application.isUnitTestMode()) &&
516           content.length <= PersistentFSConstants.FILE_LENGTH_TO_CACHE_THRESHOLD) {
517         synchronized (myInputLock) {
518           writeContent(file, new ByteSequence(content), delegate.isReadOnly());
519           setFlag(file, MUST_RELOAD_CONTENT, false);
520         }
521       }
522
523       return content;
524     }
525     else {
526       try {
527         final int length = (int)file.getLength();
528         assert length >= 0 : file;
529         return FileUtil.loadBytes(contentStream, length);
530       }
531       catch (IOException e) {
532         throw FSRecords.handleError(e);
533       }
534     }
535   }
536
537   @Override
538   @NotNull
539   public byte[] contentsToByteArray(int contentId) throws IOException {
540     final DataInputStream stream = readContentById(contentId);
541     assert stream != null : contentId;
542     return FileUtil.loadBytes(stream);
543   }
544
545   @Override
546   @NotNull
547   public InputStream getInputStream(@NotNull final VirtualFile file) throws IOException {
548     synchronized (myInputLock) {
549       InputStream contentStream;
550       if (mustReloadContent(file) || (contentStream = readContent(file)) == null) {
551         NewVirtualFileSystem delegate = getDelegate(file);
552         long len = reloadLengthFromDelegate(file, delegate);
553         InputStream nativeStream = delegate.getInputStream(file);
554
555         if (len > PersistentFSConstants.FILE_LENGTH_TO_CACHE_THRESHOLD) return nativeStream;
556         return createReplicator(file, nativeStream, len, delegate.isReadOnly());
557       }
558       else {
559         return contentStream;
560       }
561     }
562   }
563
564   private static long reloadLengthFromDelegate(@NotNull VirtualFile file, @NotNull NewVirtualFileSystem delegate) {
565     final long len = delegate.getLength(file);
566     FSRecords.setLength(getFileId(file), len);
567     return len;
568   }
569
570   private InputStream createReplicator(@NotNull final VirtualFile file,
571                                        final InputStream nativeStream,
572                                        final long fileLength,
573                                        final boolean readOnly) throws IOException {
574     if (nativeStream instanceof BufferExposingByteArrayInputStream) {
575       // optimization
576       BufferExposingByteArrayInputStream  byteStream = (BufferExposingByteArrayInputStream )nativeStream;
577       byte[] bytes = byteStream.getInternalBuffer();
578       storeContentToStorage(fileLength, file, readOnly, bytes, bytes.length);
579       return nativeStream;
580     }
581     @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
582     final BufferExposingByteArrayOutputStream cache = new BufferExposingByteArrayOutputStream((int)fileLength);
583     return new ReplicatorInputStream(nativeStream, cache) {
584       @Override
585       public void close() throws IOException {
586         super.close();
587         storeContentToStorage(fileLength, file, readOnly, cache.getInternalBuffer(), cache.size());
588       }
589     };
590   }
591
592   private void storeContentToStorage(long fileLength,
593                                      @NotNull VirtualFile file,
594                                      boolean readOnly, @NotNull byte[] bytes, int bytesLength)
595     throws IOException {
596     synchronized (myInputLock) {
597       if (bytesLength == fileLength) {
598         writeContent(file, new ByteSequence(bytes, 0, bytesLength), readOnly);
599         setFlag(file, MUST_RELOAD_CONTENT, false);
600       }
601       else {
602         setFlag(file, MUST_RELOAD_CONTENT, true);
603       }
604     }
605   }
606
607   private static boolean mustReloadContent(@NotNull VirtualFile file) {
608     int fileId = getFileId(file);
609     return checkFlag(fileId, MUST_RELOAD_CONTENT) || FSRecords.getLength(fileId) == -1L;
610   }
611
612   @Override
613   @NotNull
614   public OutputStream getOutputStream(@NotNull final VirtualFile file,
615                                       final Object requestor,
616                                       final long modStamp,
617                                       final long timeStamp) throws IOException {
618     return new ByteArrayOutputStream() {
619       private boolean closed; // protection against user calling .close() twice
620
621       @Override
622       public void close() throws IOException {
623         if (closed) return;
624         super.close();
625
626         VFileContentChangeEvent event = new VFileContentChangeEvent(requestor, file, file.getModificationStamp(), modStamp, false);
627         List<VFileContentChangeEvent> events = Collections.singletonList(event);
628         BulkFileListener publisher = myEventBus.syncPublisher(VirtualFileManager.VFS_CHANGES);
629         publisher.before(events);
630
631         NewVirtualFileSystem delegate = getDelegate(file);
632         OutputStream ioFileStream = delegate.getOutputStream(file, requestor, modStamp, timeStamp);
633         // FSRecords.ContentOutputStream already buffered, no need to wrap in BufferedStream
634         OutputStream persistenceStream = writeContent(file, delegate.isReadOnly());
635
636         try {
637           persistenceStream.write(buf, 0, count);
638         }
639         finally {
640           try {
641             ioFileStream.write(buf, 0, count);
642           }
643           finally {
644             closed = true;
645             persistenceStream.close();
646             ioFileStream.close();
647
648             executeTouch(file, false, event.getModificationStamp());
649             publisher.after(events);
650           }
651         }
652       }
653     };
654   }
655
656   @Override
657   public int acquireContent(@NotNull VirtualFile file) {
658     return FSRecords.acquireFileContent(getFileId(file));
659   }
660
661   @Override
662   public void releaseContent(int contentId) {
663     FSRecords.releaseContent(contentId);
664   }
665
666   @Override
667   public int getCurrentContentId(@NotNull VirtualFile file) {
668     return FSRecords.getContentId(getFileId(file));
669   }
670
671   @Override
672   public void moveFile(final Object requestor, @NotNull final VirtualFile file, @NotNull final VirtualFile newParent) throws IOException {
673     getDelegate(file).moveFile(requestor, file, newParent);
674     processEvent(new VFileMoveEvent(requestor, file, newParent));
675   }
676
677   private void processEvent(@NotNull VFileEvent event) {
678     processEvents(Collections.singletonList(event));
679   }
680
681   private static class EventWrapper {
682     private final VFileDeleteEvent event;
683     private final int id;
684
685     private EventWrapper(final VFileDeleteEvent event, final int id) {
686       this.event = event;
687       this.id = id;
688     }
689   }
690
691   @NotNull private static final Comparator<EventWrapper> DEPTH_COMPARATOR = new Comparator<EventWrapper>() {
692     @Override
693     public int compare(@NotNull final EventWrapper o1, @NotNull final EventWrapper o2) {
694       return o1.event.getFileDepth() - o2.event.getFileDepth();
695     }
696   };
697
698   @NotNull
699   private static List<VFileEvent> validateEvents(@NotNull List<VFileEvent> events) {
700     final List<EventWrapper> deletionEvents = ContainerUtil.newArrayList();
701     for (int i = 0, size = events.size(); i < size; i++) {
702       final VFileEvent event = events.get(i);
703       if (event instanceof VFileDeleteEvent && event.isValid()) {
704         deletionEvents.add(new EventWrapper((VFileDeleteEvent)event, i));
705       }
706     }
707
708     final TIntHashSet invalidIDs;
709     if (deletionEvents.isEmpty()) {
710       invalidIDs = EmptyIntHashSet.INSTANCE;
711     }
712     else {
713       ContainerUtil.quickSort(deletionEvents, DEPTH_COMPARATOR);
714
715       invalidIDs = new TIntHashSet(deletionEvents.size());
716       final Set<VirtualFile> dirsToBeDeleted = new THashSet<VirtualFile>(deletionEvents.size());
717       nextEvent:
718       for (EventWrapper wrapper : deletionEvents) {
719         final VirtualFile candidate = wrapper.event.getFile();
720         VirtualFile parent = candidate;
721         while (parent != null) {
722           if (dirsToBeDeleted.contains(parent)) {
723             invalidIDs.add(wrapper.id);
724             continue nextEvent;
725           }
726           parent = parent.getParent();
727         }
728
729         if (candidate.isDirectory()) {
730           dirsToBeDeleted.add(candidate);
731         }
732       }
733     }
734
735     final List<VFileEvent> filtered = new ArrayList<VFileEvent>(events.size() - invalidIDs.size());
736     for (int i = 0, size = events.size(); i < size; i++) {
737       final VFileEvent event = events.get(i);
738       if (event.isValid() && !(event instanceof VFileDeleteEvent && invalidIDs.contains(i))) {
739         filtered.add(event);
740       }
741     }
742     return filtered;
743   }
744
745   @Override
746   public void processEvents(@NotNull List<VFileEvent> events) {
747     ApplicationManager.getApplication().assertWriteAccessAllowed();
748
749     List<VFileEvent> validated = validateEvents(events);
750
751     BulkFileListener publisher = myEventBus.syncPublisher(VirtualFileManager.VFS_CHANGES);
752     publisher.before(validated);
753
754     THashMap<VirtualFile, List<VFileEvent>> parentToChildrenEventsChanges = null;
755     for (VFileEvent event : validated) {
756       VirtualFile changedParent = null;
757       if (event instanceof VFileCreateEvent) {
758         changedParent = ((VFileCreateEvent)event).getParent();
759         ((VFileCreateEvent)event).resetCache();
760       }
761       else if (event instanceof VFileDeleteEvent) {
762         changedParent = ((VFileDeleteEvent)event).getFile().getParent();
763       }
764
765       if (changedParent != null) {
766         if (parentToChildrenEventsChanges == null) parentToChildrenEventsChanges = new THashMap<VirtualFile, List<VFileEvent>>();
767         List<VFileEvent> parentChildrenChanges = parentToChildrenEventsChanges.get(changedParent);
768         if (parentChildrenChanges == null) {
769           parentToChildrenEventsChanges.put(changedParent, parentChildrenChanges = new SmartList<VFileEvent>());
770         }
771         parentChildrenChanges.add(event);
772       }
773       else {
774         applyEvent(event);
775       }
776     }
777
778     if (parentToChildrenEventsChanges != null) {
779       parentToChildrenEventsChanges.forEachEntry(new TObjectObjectProcedure<VirtualFile, List<VFileEvent>>() {
780         @Override
781         public boolean execute(VirtualFile parent, List<VFileEvent> childrenEvents) {
782           applyChildrenChangeEvents(parent, childrenEvents);
783           return true;
784         }
785       });
786       parentToChildrenEventsChanges.clear();
787     }
788
789     publisher.after(validated);
790   }
791
792   private void applyChildrenChangeEvents(VirtualFile parent, List<VFileEvent> events) {
793     final NewVirtualFileSystem delegate = getDelegate(parent);
794     TIntArrayList childrenIdsUpdated = new TIntArrayList();
795     List<VirtualFile> childrenToBeUpdated = new SmartList<VirtualFile>();
796
797     final int parentId = getFileId(parent);
798     assert parentId != 0;
799     TIntHashSet parentChildrenIds = new TIntHashSet(FSRecords.list(parentId));
800     boolean hasRemovedChildren = false;
801
802     for (VFileEvent event : events) {
803       if (event instanceof VFileCreateEvent) {
804         String name = ((VFileCreateEvent)event).getChildName();
805         final VirtualFile fake = new FakeVirtualFile(parent, name);
806         final FileAttributes attributes = delegate.getAttributes(fake);
807
808         if (attributes != null) {
809           final int childId = createAndFillRecord(delegate, fake, parentId, attributes);
810           assert parent instanceof VirtualDirectoryImpl : parent;
811           final VirtualDirectoryImpl dir = (VirtualDirectoryImpl)parent;
812           VirtualFileSystemEntry child = dir.createChild(name, childId, dir.getFileSystem());
813           childrenToBeUpdated.add(child);
814           childrenIdsUpdated.add(childId);
815           parentChildrenIds.add(childId);
816         }
817       }
818       else if (event instanceof VFileDeleteEvent) {
819         VirtualFile file = ((VFileDeleteEvent)event).getFile();
820         if (!file.exists()) {
821           LOG.error("Deleting a file, which does not exist: " + file.getPath());
822           continue;
823         }
824
825         hasRemovedChildren = true;
826         int id = getFileId(file);
827
828         childrenToBeUpdated.add(file);
829         childrenIdsUpdated.add(-id);
830         parentChildrenIds.remove(id);
831       }
832     }
833
834     FSRecords.updateList(parentId, parentChildrenIds.toArray());
835
836     if (hasRemovedChildren) clearIdCache();
837     VirtualDirectoryImpl parentImpl = (VirtualDirectoryImpl)parent;
838
839     for (int i = 0, len = childrenIdsUpdated.size(); i < len; ++i) {
840       final int childId = childrenIdsUpdated.get(i);
841       final VirtualFile childFile = childrenToBeUpdated.get(i);
842
843       if (childId > 0) {
844         parentImpl.addChild((VirtualFileSystemEntry)childFile);
845       }
846       else {
847         FSRecords.deleteRecordRecursively(-childId);
848         parentImpl.removeChild(childFile);
849         invalidateSubtree(childFile);
850       }
851     }
852   }
853
854   @Override
855   @Nullable
856   public VirtualFileSystemEntry findRoot(@NotNull String basePath, @NotNull NewVirtualFileSystem fs) {
857     if (basePath.isEmpty()) {
858       LOG.error("Invalid root, fs=" + fs);
859       return null;
860     }
861
862     String rootUrl = normalizeRootUrl(basePath, fs);
863
864     myRootsLock.readLock().lock();
865     try {
866       VirtualFileSystemEntry root = myRoots.get(rootUrl);
867       if (root != null) return root;
868     }
869     finally {
870       myRootsLock.readLock().unlock();
871     }
872
873     final VirtualFileSystemEntry newRoot;
874     int rootId = FSRecords.findRootRecord(rootUrl);
875
876     VfsData.Segment segment = VfsData.getSegment(rootId, true);
877     VfsData.DirectoryData directoryData = new VfsData.DirectoryData();
878     if (fs instanceof JarFileSystem) {
879       String parentPath = basePath.substring(0, basePath.indexOf(JarFileSystem.JAR_SEPARATOR));
880       VirtualFile parentFile = LocalFileSystem.getInstance().findFileByPath(parentPath);
881       if (parentFile == null) return null;
882       FileType type = FileTypeRegistry.getInstance().getFileTypeByFileName(parentFile.getName());
883       if (type != FileTypes.ARCHIVE) return null;
884       newRoot = new JarRoot(fs, rootId, segment, directoryData, parentFile);
885     }
886     else {
887       newRoot = new FsRoot(fs, rootId, segment, directoryData, basePath);
888     }
889
890     FileAttributes attributes = fs.getAttributes(new StubVirtualFile() {
891       @NotNull
892       @Override
893       public String getPath() {
894         return newRoot.getPath();
895       }
896
897       @Nullable
898       @Override
899       public VirtualFile getParent() {
900         return null;
901       }
902     });
903     if (attributes == null || !attributes.isDirectory()) {
904       return null;
905     }
906
907     boolean mark = false;
908
909     myRootsLock.writeLock().lock();
910     try {
911       VirtualFileSystemEntry root = myRoots.get(rootUrl);
912       if (root != null) return root;
913
914       VfsData.initFile(rootId, segment, -1, directoryData);
915       mark = writeAttributesToRecord(rootId, 0, newRoot, fs, attributes);
916
917       myRoots.put(rootUrl, newRoot);
918       myRootsById.put(rootId, newRoot);
919     }
920     finally {
921       myRootsLock.writeLock().unlock();
922     }
923
924     if (!mark && attributes.lastModified != FSRecords.getTimestamp(rootId)) {
925       newRoot.markDirtyRecursively();
926     }
927
928     LOG.assertTrue(rootId == newRoot.getId(), "root=" + newRoot + " expected=" + rootId + " actual=" + newRoot.getId());
929
930     return newRoot;
931   }
932
933   @NotNull
934   private static String normalizeRootUrl(@NotNull String basePath, @NotNull NewVirtualFileSystem fs) {
935     // need to protect against relative path of the form "/x/../y"
936     return UriUtil.trimTrailingSlashes(
937       fs.getProtocol() + URLUtil.SCHEME_SEPARATOR + VfsImplUtil.normalize(fs, FileUtil.toCanonicalPath(basePath)));
938   }
939
940   @Override
941   public void clearIdCache() {
942     myIdToDirCache.clear();
943   }
944
945   private static final int DEPTH_LIMIT = 75;
946
947   @Override
948   @Nullable
949   public NewVirtualFile findFileById(final int id) {
950     return findFileById(id, false, null, 0);
951   }
952
953   @Override
954   public NewVirtualFile findFileByIdIfCached(final int id) {
955     return findFileById(id, true, null, 0);
956   }
957
958   @Nullable
959   private VirtualFileSystemEntry findFileById(int id, boolean cachedOnly, TIntArrayList visited, int mask) {
960     VirtualFileSystemEntry cached = myIdToDirCache.get(id);
961     if (cached != null) return cached;
962
963     if (visited != null && (visited.size() >= DEPTH_LIMIT || (mask & id) == id && visited.contains(id))) {
964       @NonNls String sb = "Dead loop detected in persistent FS (id=" + id + " cached-only=" + cachedOnly + "):";
965       for (int i = 0; i < visited.size(); i++) {
966         int _id = visited.get(i);
967         sb += "\n  " + _id + " '" + getName(_id) + "' " +
968           String.format("%02x", getFileAttributes(_id)) + ' ' + myIdToDirCache.containsKey(_id);
969       }
970       LOG.error(sb);
971       return null;
972     }
973
974     int parentId = getParent(id);
975     if (parentId >= id) {
976       if (visited == null) visited = new TIntArrayList(DEPTH_LIMIT);
977     }
978     if (visited != null)  visited.add(id);
979
980     VirtualFileSystemEntry result;
981     if (parentId == 0) {
982       myRootsLock.readLock().lock();
983       try {
984         result = myRootsById.get(id);
985       }
986       finally {
987         myRootsLock.readLock().unlock();
988       }
989     }
990     else {
991       VirtualFileSystemEntry parentFile = findFileById(parentId, cachedOnly, visited, mask | id);
992       if (parentFile instanceof VirtualDirectoryImpl) {
993         result = ((VirtualDirectoryImpl)parentFile).findChildById(id, cachedOnly);
994       }
995       else {
996         result = null;
997       }
998     }
999
1000     if (result != null && result.isDirectory()) {
1001       VirtualFileSystemEntry old = myIdToDirCache.put(id, result);
1002       if (old != null) result = old;
1003     }
1004     return result;
1005   }
1006
1007   @Override
1008   @NotNull
1009   public VirtualFile[] getRoots() {
1010     myRootsLock.readLock().lock();
1011     try {
1012       Collection<VirtualFileSystemEntry> roots = myRoots.values();
1013       return VfsUtilCore.toVirtualFileArray(roots);
1014     }
1015     finally {
1016       myRootsLock.readLock().unlock();
1017     }
1018   }
1019
1020   @Override
1021   @NotNull
1022   public VirtualFile[] getRoots(@NotNull final NewVirtualFileSystem fs) {
1023     final List<VirtualFile> roots = new ArrayList<VirtualFile>();
1024
1025     myRootsLock.readLock().lock();
1026     try {
1027       for (NewVirtualFile root : myRoots.values()) {
1028         if (root.getFileSystem() == fs) {
1029           roots.add(root);
1030         }
1031       }
1032     }
1033     finally {
1034       myRootsLock.readLock().unlock();
1035     }
1036
1037     return VfsUtilCore.toVirtualFileArray(roots);
1038   }
1039
1040   @Override
1041   @NotNull
1042   public VirtualFile[] getLocalRoots() {
1043     List<VirtualFile> roots = ContainerUtil.newSmartList();
1044
1045     myRootsLock.readLock().lock();
1046     try {
1047       for (NewVirtualFile root : myRoots.values()) {
1048         if (root.isInLocalFileSystem() && !(root.getFileSystem() instanceof TempFileSystem)) {
1049           roots.add(root);
1050         }
1051       }
1052     }
1053     finally {
1054       myRootsLock.readLock().unlock();
1055     }
1056
1057     return VfsUtilCore.toVirtualFileArray(roots);
1058   }
1059
1060   private VirtualFileSystemEntry applyEvent(@NotNull VFileEvent event) {
1061     try {
1062       if (event instanceof VFileCreateEvent) {
1063         final VFileCreateEvent createEvent = (VFileCreateEvent)event;
1064         return executeCreateChild(createEvent.getParent(), createEvent.getChildName());
1065       }
1066       else if (event instanceof VFileDeleteEvent) {
1067         final VFileDeleteEvent deleteEvent = (VFileDeleteEvent)event;
1068         executeDelete(deleteEvent.getFile());
1069       }
1070       else if (event instanceof VFileContentChangeEvent) {
1071         final VFileContentChangeEvent contentUpdateEvent = (VFileContentChangeEvent)event;
1072         executeTouch(contentUpdateEvent.getFile(), contentUpdateEvent.isFromRefresh(), contentUpdateEvent.getModificationStamp());
1073       }
1074       else if (event instanceof VFileCopyEvent) {
1075         final VFileCopyEvent copyEvent = (VFileCopyEvent)event;
1076         return executeCreateChild(copyEvent.getNewParent(), copyEvent.getNewChildName());
1077       }
1078       else if (event instanceof VFileMoveEvent) {
1079         final VFileMoveEvent moveEvent = (VFileMoveEvent)event;
1080         executeMove(moveEvent.getFile(), moveEvent.getNewParent());
1081       }
1082       else if (event instanceof VFilePropertyChangeEvent) {
1083         final VFilePropertyChangeEvent propertyChangeEvent = (VFilePropertyChangeEvent)event;
1084         if (VirtualFile.PROP_NAME.equals(propertyChangeEvent.getPropertyName())) {
1085           executeRename(propertyChangeEvent.getFile(), (String)propertyChangeEvent.getNewValue());
1086         }
1087         else if (VirtualFile.PROP_WRITABLE.equals(propertyChangeEvent.getPropertyName())) {
1088           executeSetWritable(propertyChangeEvent.getFile(), ((Boolean)propertyChangeEvent.getNewValue()).booleanValue());
1089         }
1090         else if (VirtualFile.PROP_HIDDEN.equals(propertyChangeEvent.getPropertyName())) {
1091           executeSetHidden(propertyChangeEvent.getFile(), ((Boolean)propertyChangeEvent.getNewValue()).booleanValue());
1092         }
1093         else if (VirtualFile.PROP_SYMLINK_TARGET.equals(propertyChangeEvent.getPropertyName())) {
1094           executeSetTarget(propertyChangeEvent.getFile(), (String)propertyChangeEvent.getNewValue());
1095         }
1096       }
1097     }
1098     catch (Exception e) {
1099       // Exception applying single event should not prevent other events from applying.
1100       LOG.error(e);
1101     }
1102     return null;
1103   }
1104
1105   @NotNull
1106   @NonNls
1107   public String toString() {
1108     return "PersistentFS";
1109   }
1110
1111   private static VirtualFileSystemEntry executeCreateChild(@NotNull VirtualFile parent, @NotNull String name) {
1112     final NewVirtualFileSystem delegate = getDelegate(parent);
1113     final VirtualFile fake = new FakeVirtualFile(parent, name);
1114     final FileAttributes attributes = delegate.getAttributes(fake);
1115     if (attributes != null) {
1116       final int parentId = getFileId(parent);
1117       final int childId = createAndFillRecord(delegate, fake, parentId, attributes);
1118       appendIdToParentList(parentId, childId);
1119       assert parent instanceof VirtualDirectoryImpl : parent;
1120       final VirtualDirectoryImpl dir = (VirtualDirectoryImpl)parent;
1121       VirtualFileSystemEntry child = dir.createChild(name, childId, dir.getFileSystem());
1122       dir.addChild(child);
1123       return child;
1124     }
1125     return null;
1126   }
1127
1128   private static int createAndFillRecord(@NotNull NewVirtualFileSystem delegateSystem,
1129                                          @NotNull VirtualFile delegateFile,
1130                                          int parentId,
1131                                          @NotNull FileAttributes attributes) {
1132     final int childId = FSRecords.createRecord();
1133     writeAttributesToRecord(childId, parentId, delegateFile, delegateSystem, attributes);
1134     return childId;
1135   }
1136
1137   private static void appendIdToParentList(final int parentId, final int childId) {
1138     int[] childrenList = FSRecords.list(parentId);
1139     childrenList = ArrayUtil.append(childrenList, childId);
1140     FSRecords.updateList(parentId, childrenList);
1141   }
1142
1143   private void executeDelete(@NotNull VirtualFile file) {
1144     if (!file.exists()) {
1145       LOG.error("Deleting a file, which does not exist: " + file.getPath());
1146       return;
1147     }
1148     clearIdCache();
1149
1150     int id = getFileId(file);
1151
1152     final VirtualFile parent = file.getParent();
1153     final int parentId = parent == null ? 0 : getFileId(parent);
1154
1155     if (parentId == 0) {
1156       String rootUrl = normalizeRootUrl(file.getPath(), (NewVirtualFileSystem)file.getFileSystem());
1157       myRootsLock.writeLock().lock();
1158       try {
1159         myRoots.remove(rootUrl);
1160         myRootsById.remove(id);
1161         FSRecords.deleteRootRecord(id);
1162       }
1163       finally {
1164         myRootsLock.writeLock().unlock();
1165       }
1166     }
1167     else {
1168       removeIdFromParentList(parentId, id, parent, file);
1169       VirtualDirectoryImpl directory = (VirtualDirectoryImpl)file.getParent();
1170       assert directory != null : file;
1171       directory.removeChild(file);
1172     }
1173
1174     FSRecords.deleteRecordRecursively(id);
1175
1176     invalidateSubtree(file);
1177   }
1178
1179   private static void invalidateSubtree(@NotNull VirtualFile file) {
1180     final VirtualFileSystemEntry impl = (VirtualFileSystemEntry)file;
1181     impl.invalidate();
1182     for (VirtualFile child : impl.getCachedChildren()) {
1183       invalidateSubtree(child);
1184     }
1185   }
1186
1187   private static void removeIdFromParentList(final int parentId, final int id, @NotNull VirtualFile parent, VirtualFile file) {
1188     int[] childList = FSRecords.list(parentId);
1189
1190     int index = ArrayUtil.indexOf(childList, id);
1191     if (index == -1) {
1192       throw new RuntimeException("Cannot find child (" + id + ")" + file
1193                                  + "\n\tin (" + parentId + ")" + parent
1194                                  + "\n\tactual children:" + Arrays.toString(childList));
1195     }
1196     childList = ArrayUtil.remove(childList, index);
1197     FSRecords.updateList(parentId, childList);
1198   }
1199
1200   private static void executeRename(@NotNull VirtualFile file, @NotNull final String newName) {
1201     final int id = getFileId(file);
1202     FSRecords.setName(id, newName);
1203     ((VirtualFileSystemEntry)file).setNewName(newName);
1204   }
1205
1206   private static void executeSetWritable(@NotNull VirtualFile file, boolean writableFlag) {
1207     setFlag(file, IS_READ_ONLY, !writableFlag);
1208     ((VirtualFileSystemEntry)file).updateProperty(VirtualFile.PROP_WRITABLE, writableFlag);
1209   }
1210
1211   private static void executeSetHidden(@NotNull VirtualFile file, boolean hiddenFlag) {
1212     setFlag(file, IS_HIDDEN, hiddenFlag);
1213     ((VirtualFileSystemEntry)file).updateProperty(VirtualFile.PROP_HIDDEN, hiddenFlag);
1214   }
1215
1216   private static void executeSetTarget(@NotNull VirtualFile file, String target) {
1217     ((VirtualFileSystemEntry)file).setLinkTarget(target);
1218   }
1219
1220   private static void setFlag(@NotNull VirtualFile file, int mask, boolean value) {
1221     setFlag(getFileId(file), mask, value);
1222   }
1223
1224   private static void setFlag(final int id, final int mask, final boolean value) {
1225     int oldFlags = FSRecords.getFlags(id);
1226     int flags = value ? oldFlags | mask : oldFlags & ~mask;
1227
1228     if (oldFlags != flags) {
1229       FSRecords.setFlags(id, flags, true);
1230     }
1231   }
1232
1233   private static boolean checkFlag(int fileId, int mask) {
1234     return (FSRecords.getFlags(fileId) & mask) != 0;
1235   }
1236
1237   private static void executeTouch(@NotNull VirtualFile file, boolean reloadContentFromDelegate, long newModificationStamp) {
1238     if (reloadContentFromDelegate) {
1239       setFlag(file, MUST_RELOAD_CONTENT, true);
1240     }
1241
1242     final NewVirtualFileSystem delegate = getDelegate(file);
1243     final FileAttributes attributes = delegate.getAttributes(file);
1244     FSRecords.setLength(getFileId(file), attributes != null ? attributes.length : DEFAULT_LENGTH);
1245     FSRecords.setTimestamp(getFileId(file), attributes != null ? attributes.lastModified : DEFAULT_TIMESTAMP);
1246
1247     ((VirtualFileSystemEntry)file).setModificationStamp(newModificationStamp);
1248   }
1249
1250   private void executeMove(@NotNull VirtualFile file, @NotNull VirtualFile newParent) {
1251     clearIdCache();
1252
1253     final int fileId = getFileId(file);
1254     final int newParentId = getFileId(newParent);
1255     final int oldParentId = getFileId(file.getParent());
1256
1257     removeIdFromParentList(oldParentId, fileId, file.getParent(), file);
1258     FSRecords.setParent(fileId, newParentId);
1259     appendIdToParentList(newParentId, fileId);
1260     ((VirtualFileSystemEntry)file).setParent(newParent);
1261   }
1262
1263   @Override
1264   public String getName(int id) {
1265     assert id > 0;
1266     return FSRecords.getName(id);
1267   }
1268
1269   @TestOnly
1270   public void cleanPersistedContents() {
1271     final int[] roots = FSRecords.listRoots();
1272     for (int root : roots) {
1273       cleanPersistedContentsRecursively(root);
1274     }
1275   }
1276
1277   @TestOnly
1278   private void cleanPersistedContentsRecursively(int id) {
1279     if (isDirectory(getFileAttributes(id))) {
1280       for (int child : FSRecords.list(id)) {
1281         cleanPersistedContentsRecursively(child);
1282       }
1283     }
1284     else {
1285       setFlag(id, MUST_RELOAD_CONTENT, true);
1286     }
1287   }
1288
1289
1290   private abstract static class AbstractRoot extends VirtualDirectoryImpl {
1291     public AbstractRoot(int id, VfsData.Segment segment, VfsData.DirectoryData data, NewVirtualFileSystem fs) {
1292       super(id, segment, data, null, fs);
1293     }
1294
1295     @NotNull
1296     @Override
1297     public abstract CharSequence getNameSequence();
1298
1299     @Override
1300     protected abstract char[] appendPathOnFileSystem(int accumulatedPathLength, int[] positionRef);
1301
1302     @Override
1303     public void setNewName(@NotNull String newName) {
1304       throw new IncorrectOperationException();
1305     }
1306
1307     @Override
1308     public final void setParent(@NotNull VirtualFile newParent) {
1309       throw new IncorrectOperationException();
1310     }
1311   }
1312
1313   private static class JarRoot extends AbstractRoot {
1314     private final VirtualFile myParentLocalFile;
1315     private final String myParentPath;
1316
1317     private JarRoot(@NotNull NewVirtualFileSystem fs, int id, VfsData.Segment segment, VfsData.DirectoryData data, VirtualFile parentLocalFile) {
1318       super(id, segment, data, fs);
1319       myParentLocalFile = parentLocalFile;
1320       myParentPath = myParentLocalFile.getPath();
1321     }
1322
1323     @NotNull
1324     @Override
1325     public CharSequence getNameSequence() {
1326       return myParentLocalFile.getName();
1327     }
1328
1329     @Override
1330     protected char[] appendPathOnFileSystem(int accumulatedPathLength, int[] positionRef) {
1331       char[] chars = new char[myParentPath.length() + JarFileSystem.JAR_SEPARATOR.length() + accumulatedPathLength];
1332       positionRef[0] = copyString(chars, positionRef[0], myParentPath);
1333       positionRef[0] = copyString(chars, positionRef[0], JarFileSystem.JAR_SEPARATOR);
1334       return chars;
1335     }
1336   }
1337
1338   private static class FsRoot extends AbstractRoot {
1339     private final String myName;
1340
1341     private FsRoot(@NotNull NewVirtualFileSystem fs, int id, VfsData.Segment segment, VfsData.DirectoryData data, @NotNull String basePath) {
1342       super(id, segment, data, fs);
1343       myName = FileUtil.toSystemIndependentName(basePath);
1344     }
1345
1346     @NotNull
1347     @Override
1348     public CharSequence getNameSequence() {
1349       return myName;
1350     }
1351
1352     @Override
1353     protected char[] appendPathOnFileSystem(int pathLength, int[] position) {
1354       String name = getName();
1355       int nameLength = name.length();
1356       int rootPathLength = pathLength + nameLength;
1357
1358       // otherwise we called this as a part of longer file path calculation and slash will be added anyway
1359       boolean appendSlash = SystemInfo.isWindows && nameLength == 2 && name.charAt(1) == ':' && pathLength == 0;
1360
1361       if (appendSlash) ++rootPathLength;
1362       char[] chars = new char[rootPathLength];
1363
1364       position[0] = copyString(chars, position[0], name);
1365
1366       if (appendSlash) {
1367         chars[position[0]++] = '/';
1368       }
1369
1370       return chars;
1371     }
1372   }
1373 }