5ff8142dff13dc77eaab806cde4602994460c421
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / vfs / newvfs / persistent / PersistentFS.java
1 /*
2  * Copyright 2000-2011 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.google.common.collect.Lists;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.application.ModalityState;
21 import com.intellij.openapi.application.ex.ApplicationEx;
22 import com.intellij.openapi.components.ApplicationComponent;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.util.Pair;
25 import com.intellij.openapi.util.ShutDownTracker;
26 import com.intellij.openapi.util.SystemInfo;
27 import com.intellij.openapi.util.io.BufferExposingByteArrayInputStream;
28 import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
29 import com.intellij.openapi.util.io.ByteSequence;
30 import com.intellij.openapi.util.io.FileUtil;
31 import com.intellij.openapi.util.registry.Registry;
32 import com.intellij.openapi.vfs.*;
33 import com.intellij.openapi.vfs.impl.win32.Win32LocalFileSystem;
34 import com.intellij.openapi.vfs.newvfs.*;
35 import com.intellij.openapi.vfs.newvfs.events.*;
36 import com.intellij.openapi.vfs.newvfs.impl.FakeVirtualFile;
37 import com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl;
38 import com.intellij.openapi.vfs.newvfs.impl.VirtualFileSystemEntry;
39 import com.intellij.util.ArrayUtil;
40 import com.intellij.util.containers.ContainerUtil;
41 import com.intellij.util.containers.StripedLockIntObjectConcurrentHashMap;
42 import com.intellij.util.io.DupOutputStream;
43 import com.intellij.util.io.ReplicatorInputStream;
44 import com.intellij.util.messages.MessageBus;
45 import gnu.trove.TIntHashSet;
46 import org.jetbrains.annotations.NonNls;
47 import org.jetbrains.annotations.NotNull;
48 import org.jetbrains.annotations.Nullable;
49
50 import java.io.*;
51 import java.util.*;
52 import java.util.concurrent.atomic.AtomicBoolean;
53
54 /**
55  * @author max
56  */
57 public class PersistentFS extends ManagingFS implements ApplicationComponent {
58   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.newvfs.persistent.PersistentFS");
59
60   private static final int CHILDREN_CACHED_FLAG = 0x01;
61   static final int IS_DIRECTORY_FLAG = 0x02;
62   private static final int IS_READ_ONLY = 0x04;
63   private static final int MUST_RELOAD_CONTENT = 0x08;
64   private static final int IS_SYMLINK = 0x10;
65   private static final int IS_SPECIAL = 0x20;
66
67   static final int ALL_VALID_FLAGS =
68     CHILDREN_CACHED_FLAG | IS_DIRECTORY_FLAG | IS_READ_ONLY | MUST_RELOAD_CONTENT | IS_SYMLINK | IS_SPECIAL;
69
70   private final MessageBus myEventsBus;
71
72   private final Map<String, VirtualFileSystemEntry> myRoots = new HashMap<String, VirtualFileSystemEntry>();
73   private VirtualFileSystemEntry myFakeRoot;
74   private final Object INPUT_LOCK = new Object();
75
76   public PersistentFS(MessageBus bus) {
77     myEventsBus = bus;
78
79     ShutDownTracker.getInstance().registerShutdownTask(new Runnable() {
80       @Override
81       public void run() {
82         performShutdown();
83       }
84     });
85   }
86
87   @Override
88   public void disposeComponent() {
89     performShutdown();
90   }
91
92   private final AtomicBoolean myShutdownPerformed = new AtomicBoolean(Boolean.FALSE);
93   private void performShutdown() {
94     if (!myShutdownPerformed.getAndSet(Boolean.TRUE)) {
95       LOG.info("VFS dispose started");
96       FSRecords.dispose();
97       LOG.info("VFS dispose completed");
98     }
99   }
100
101   @Override
102   @NonNls
103   @NotNull
104   public String getComponentName() {
105     return "app.component.PersistentFS";
106   }
107
108   @Override
109   public void initComponent() {
110     FSRecords.connect();
111   }
112
113   @Override
114   public boolean areChildrenLoaded(@NotNull final VirtualFile dir) {
115     return areChildrenLoaded(getFileId(dir));
116   }
117
118   public long getCreationTimestamp() {
119     return FSRecords.getCreationTimestamp();
120   }
121
122   private static NewVirtualFileSystem getDelegate(VirtualFile file) {
123     return (NewVirtualFileSystem)file.getFileSystem();
124   }
125
126   @Override
127   public boolean wereChildrenAccessed(@NotNull final VirtualFile dir) {
128     return FSRecords.wereChildrenAccessed(getFileId(dir));
129   }
130
131   @Override
132   @NotNull
133   public String[] list(@NotNull final VirtualFile file) {
134     int id = getFileId(file);
135     int[] childrenIds = FSRecords.list(id);
136     String[] names = listPersisted(childrenIds);
137     if (areChildrenLoaded(id)) {
138       return names;
139     }
140     Pair<String[], int[]> pair = persistAllChildren(file, id, Pair.create(names, childrenIds));
141     return pair.first;
142   }
143
144   @NotNull
145   public static String[] listPersisted(@NotNull VirtualFile file) {
146     return listPersisted(FSRecords.list(getFileId(file)));
147   }
148
149   @NotNull
150   private static String[] listPersisted(@NotNull int[] childrenIds) {
151     String[] names = ArrayUtil.newStringArray(childrenIds.length);
152     for (int i = 0; i < childrenIds.length; i++) {
153       names[i] = FSRecords.getName(childrenIds[i]);
154     }
155     return names;
156   }
157
158   @NotNull
159   private static Pair<String[],int[]> persistAllChildren(@NotNull VirtualFile file, int id, @NotNull Pair<String[],int[]> current) {
160     String[] currentNames = current.first;
161     int[] currentIds = current.second;
162
163     final NewVirtualFileSystem delegate = getDelegate(file);
164     String[] delegateNames = VfsUtil.filterNames(delegate.list(file));
165     if (delegateNames.length == 0 && currentNames.length > 0) {
166       return current;
167     }
168
169     final String[] names;
170     if (currentNames.length == 0) {
171       names = delegateNames;
172     }
173     else {
174       Set<String> allNamesSet = new LinkedHashSet<String>((currentNames.length + delegateNames.length) * 2);
175       ContainerUtil.addAll(allNamesSet, currentNames);
176       ContainerUtil.addAll(allNamesSet, delegateNames);
177       names = ArrayUtil.toStringArray(allNamesSet);
178     }
179
180     final int[] childrenIds = ArrayUtil.newIntArray(names.length);
181
182     for (int i = 0; i < names.length; i++) {
183       final String name = names[i];
184       int idx = ArrayUtil.indexOf(currentNames, name);
185       if (idx >= 0) {
186         childrenIds[i] = currentIds[idx];
187       }
188       else {
189         int childId = createAndCopyRecord(delegate, new FakeVirtualFile(file, name), id);
190         childrenIds[i] = childId;
191       }
192     }
193
194     FSRecords.updateList(id, childrenIds);
195     int flags = FSRecords.getFlags(id);
196     FSRecords.setFlags(id, flags | CHILDREN_CACHED_FLAG, true);
197
198     return Pair.create(names, childrenIds);
199   }
200
201   @NotNull
202   public static int[] listIds(@NotNull VirtualFile parent) {
203     final int parentId = getFileId(parent);
204
205     int[] ids = FSRecords.list(parentId);
206     if (!areChildrenLoaded(parentId)) {
207       String[] names = listPersisted(ids);
208       Pair<String[], int[]> pair = persistAllChildren(parent, parentId, Pair.create(names, ids));
209       return pair.second;
210     }
211
212     return ids;
213   }
214
215   @NotNull
216   public static Pair<String[],int[]> listAll(@NotNull VirtualFile parent) {
217     final int parentId = getFileId(parent);
218
219     Pair<String[], int[]> pair = FSRecords.listAll(parentId);
220     if (!areChildrenLoaded(parentId)) {
221       return persistAllChildren(parent, parentId, pair);
222     }
223
224     return pair;
225   }
226
227
228   private static boolean areChildrenLoaded(final int parentId) {
229     final int mask = CHILDREN_CACHED_FLAG;
230     return (FSRecords.getFlags(parentId) & mask) != 0;
231   }
232
233   @Override
234   @Nullable
235   public DataInputStream readAttribute(@NotNull final VirtualFile file, @NotNull final FileAttribute att) {
236     return FSRecords.readAttribute(getFileId(file), att.getId());
237   }
238
239   @Override
240   @NotNull
241   public DataOutputStream writeAttribute(@NotNull final VirtualFile file, @NotNull final FileAttribute att) {
242     return FSRecords.writeAttribute(getFileId(file), att.getId(), att.isFixedSize());
243   }
244
245   @Nullable
246   private static DataInputStream readContent(VirtualFile file) {
247     return FSRecords.readContent(getFileId(file));
248   }
249
250   @Nullable
251   private static DataInputStream readContentById(int contentId) {
252     return FSRecords.readContentById(contentId);
253   }
254
255   private static DataOutputStream writeContent(VirtualFile file, boolean readOnly) {
256     return FSRecords.writeContent(getFileId(file), readOnly);
257   }
258
259   private static void writeContent(VirtualFile file, ByteSequence content, boolean readOnly) throws IOException {
260     FSRecords.writeContent(getFileId(file), content, readOnly);
261   }
262
263   public int storeUnlinkedContent(byte[] bytes) {
264     return FSRecords.storeUnlinkedContent(bytes);
265   }
266
267   @Override
268   public int getModificationCount(@NotNull final VirtualFile file) {
269     final int id = getFileId(file);
270     return FSRecords.getModCount(id);
271   }
272
273   @Override
274   public int getCheapFileSystemModificationCount() {
275     return FSRecords.getLocalModCount();
276   }
277
278   @Override
279   public int getFilesystemModificationCount() {
280     return FSRecords.getModCount();
281   }
282
283   private static boolean copyRecordFromDelegateFS(final int id, final int parentId, final VirtualFile file, NewVirtualFileSystem delegate) {
284     String name = file.getName();
285
286     if (name.length() > 0 && namesEqual(delegate, name, FSRecords.getName(id))) return false; // TODO: Handle root attributes change.
287
288     if (name.length() == 0) {            // TODO: hack
289       if (areChildrenLoaded(id)) return false;
290     }
291
292     FSRecords.setParent(id, parentId);
293     FSRecords.setName(id, name);
294
295     delegate = replaceWithNativeFS(delegate);
296
297     FSRecords.setTimestamp(id, delegate.getTimeStamp(file));
298
299     boolean directory = delegate.isDirectory(file);
300
301     FSRecords.setLength(id, directory ? -1L : delegate.getLength(file));
302
303     FSRecords.setFlags(id, (directory ? IS_DIRECTORY_FLAG : 0) |
304                            (delegate.isWritable(file) ? 0 : IS_READ_ONLY) |
305                            (delegate.isSymLink(file) ? IS_SYMLINK : 0) |
306                            (delegate.isSpecialFile(file) ? IS_SPECIAL : 0), true);
307
308     return true;
309   }
310
311   @Override
312   public boolean isDirectory(@NotNull final VirtualFile file) {
313     final int id = getFileId(file);
314     return isDirectory(id);
315   }
316
317   public static boolean isDirectory(final int id) {
318     assert id > 0;
319     return (FSRecords.getFlags(id) & IS_DIRECTORY_FLAG) != 0;
320   }
321
322   public static boolean isSymLink(final int id) {
323     assert id > 0;
324     return (FSRecords.getFlags(id) & IS_SYMLINK) != 0;
325   }
326
327   private static int getParent(final int id) {
328     assert id > 0;
329     return FSRecords.getParent(id);
330   }
331
332   private static boolean namesEqual(VirtualFileSystem fs, String n1, String n2) {
333     return ((NewVirtualFileSystem)fs).isCaseSensitive() ? n1.equals(n2) : n1.equalsIgnoreCase(n2);
334   }
335
336   @Override
337   public boolean exists(@NotNull final VirtualFile fileOrDirectory) {
338     return ((VirtualFileWithId)fileOrDirectory).getId() > 0;
339   }
340
341   @Override
342   public long getTimeStamp(@NotNull final VirtualFile file) {
343     final int id = getFileId(file);
344     return FSRecords.getTimestamp(id);
345   }
346
347   @Override
348   public void setTimeStamp(@NotNull final VirtualFile file, final long modstamp) throws IOException {
349     final int id = getFileId(file);
350
351     FSRecords.setTimestamp(id, modstamp);
352     getDelegate(file).setTimeStamp(file, modstamp);
353   }
354
355   private static int getFileId(final VirtualFile file) {
356     final int id = ((VirtualFileWithId)file).getId();
357     if (id <= 0) {
358       throw new InvalidVirtualFileAccessException(file);
359     }
360     return id;
361   }
362
363   @Override
364   public boolean isSymLink(@NotNull VirtualFile file) {
365     return (FSRecords.getFlags(getFileId(file)) & IS_SYMLINK) != 0;
366   }
367
368   @Override
369   public String resolveSymLink(@NotNull VirtualFile file) {
370     throw new UnsupportedOperationException();
371   }
372
373   @Override
374   public boolean isSpecialFile(@NotNull VirtualFile file) {
375     return (FSRecords.getFlags(getFileId(file)) & IS_SPECIAL) != 0;
376   }
377
378   @Override
379   public boolean isWritable(@NotNull final VirtualFile file) {
380     return (FSRecords.getFlags(getFileId(file)) & IS_READ_ONLY) == 0;
381   }
382
383   @Override
384   public void setWritable(@NotNull final VirtualFile file, final boolean writableFlag) throws IOException {
385     getDelegate(file).setWritable(file, writableFlag);
386     processEvent(new VFilePropertyChangeEvent(this, file, VirtualFile.PROP_WRITABLE, isWritable(file), writableFlag, false));
387   }
388
389   public static int getId(final VirtualFile parent, final String childName, NewVirtualFileSystem delegate) {
390     final int parentId = getFileId(parent);
391
392     final int[] children = FSRecords.list(parentId);
393     for (final int childId : children) {
394       if (namesEqual(delegate, childName, FSRecords.getName(childId))) return childId;
395     }
396
397     VirtualFile fake = new FakeVirtualFile(parent, childName);
398     if (delegate.exists(fake)) {
399       int child = createAndCopyRecord(delegate, fake, parentId);
400       FSRecords.updateList(parentId, ArrayUtil.append(children, child));
401       return child;
402     }
403
404     return 0;
405   }
406
407   @Override
408   public long getLength(@NotNull final VirtualFile file) {
409     final int id = getFileId(file);
410
411     long len = FSRecords.getLength(id);
412     if (len == -1) {
413       len = (int)getDelegate(file).getLength(file);
414       FSRecords.setLength(id, len);
415     }
416
417     return len;
418   }
419
420   @Override
421   public VirtualFile copyFile(final Object requestor, @NotNull final VirtualFile file, @NotNull final VirtualFile newParent, @NotNull final String copyName)
422     throws IOException {
423     getDelegate(file).copyFile(requestor, file, newParent, copyName);
424     processEvent(new VFileCopyEvent(requestor, file, newParent, copyName));
425
426     final VirtualFile child = newParent.findChild(copyName);
427     if (child == null) {
428       throw new IOException("Cannot create child");
429     }
430     return child;
431   }
432
433   @Override
434   public VirtualFile createChildDirectory(final Object requestor, @NotNull final VirtualFile parent, @NotNull final String dir) throws IOException {
435     getDelegate(parent).createChildDirectory(requestor, parent, dir);
436     processEvent(new VFileCreateEvent(requestor, parent, dir, true, false));
437
438     final VirtualFile child = parent.findChild(dir);
439     if (child == null) {
440       throw new IOException("Cannot create child directory '" + dir + "' at " + parent.getPath());
441     }
442     return child;
443   }
444
445   @Override
446   public VirtualFile createChildFile(final Object requestor, @NotNull final VirtualFile parent, @NotNull final String file) throws IOException {
447     getDelegate(parent).createChildFile(requestor, parent, file);
448     processEvent(new VFileCreateEvent(requestor, parent, file, false, false));
449
450     final VirtualFile child = parent.findChild(file);
451     if (child == null) {
452       throw new IOException("Cannot create child file '" + file + "' at " + parent.getPath());
453     }
454     return child;
455   }
456
457   @Override
458   public void deleteFile(final Object requestor, @NotNull final VirtualFile file) throws IOException {
459     final NewVirtualFileSystem delegate = getDelegate(file);
460     delegate.deleteFile(requestor, file);
461
462     if (!delegate.exists(file)) {
463       processEvent(new VFileDeleteEvent(requestor, file, false));
464     }
465   }
466
467   @Override
468   public void renameFile(final Object requestor, @NotNull final VirtualFile file, @NotNull final String newName) throws IOException {
469     getDelegate(file).renameFile(requestor, file, newName);
470     processEvent(new VFilePropertyChangeEvent(requestor, file, VirtualFile.PROP_NAME, file.getName(), newName, false));
471   }
472
473   @Override
474   @NotNull
475   public byte[] contentsToByteArray(@NotNull final VirtualFile file) throws IOException {
476     return contentsToByteArray(file, true);
477   }
478
479   @NotNull
480   public byte[] contentsToByteArray(final VirtualFile file, boolean cacheContent) throws IOException {
481     InputStream contentStream = null;
482     boolean reloadFromDelegate;
483     synchronized (INPUT_LOCK) {
484       reloadFromDelegate = mustReloadContent(file) || (contentStream = readContent(file)) == null;
485     }
486
487     if (reloadFromDelegate) {
488       final NewVirtualFileSystem delegate = getDelegate(file);
489       FSRecords.setLength(getFileId(file), delegate.getLength(file));
490       final byte[] content = delegate.contentsToByteArray(file);
491
492       ApplicationEx application = (ApplicationEx)ApplicationManager.getApplication();
493       // we should cache every local files content
494       // because the local history feature is currently depends on this cache
495       if ((!delegate.isReadOnly() || !application.isInternal() && !application.isUnitTestMode()) &&
496           content.length <= PersistentFSConstants.FILE_LENGTH_TO_CACHE_THRESHOLD) {
497         synchronized (INPUT_LOCK) {
498           writeContent(file, new ByteSequence(content), delegate.isReadOnly());
499           setFlag(file, MUST_RELOAD_CONTENT, false);
500         }
501       }
502
503       return content;
504     }
505     else {
506       try {
507         return FileUtil.loadBytes(contentStream, (int)file.getLength());
508       }
509       catch (IOException e) {
510         throw FSRecords.handleError(e);
511       }
512     }
513   }
514
515   public byte[] contentsToByteArray(int contentId) throws IOException {
516     return FileUtil.loadBytes(readContentById(contentId));
517   }
518
519   @Override
520   @NotNull
521   public InputStream getInputStream(@NotNull final VirtualFile file) throws IOException {
522     synchronized (INPUT_LOCK) {
523       InputStream contentStream;
524       if (mustReloadContent(file) || (contentStream = readContent(file)) == null) {
525         final NewVirtualFileSystem delegate = getDelegate(file);
526         final long len = delegate.getLength(file);
527         FSRecords.setLength(getFileId(file), len);
528         final InputStream nativeStream = delegate.getInputStream(file);
529
530         if (len > PersistentFSConstants.FILE_LENGTH_TO_CACHE_THRESHOLD) return nativeStream;
531         return createReplicator(file, nativeStream, len, delegate.isReadOnly());
532       }
533       else {
534         return contentStream;
535       }
536     }
537   }
538
539   private InputStream createReplicator(final VirtualFile file, final InputStream nativeStream, final long fileLength, final boolean readOnly)
540     throws IOException {
541     if (nativeStream instanceof BufferExposingByteArrayInputStream) {
542       // optimization
543       BufferExposingByteArrayInputStream  byteStream = (BufferExposingByteArrayInputStream )nativeStream;
544       byte[] bytes = byteStream.getInternalBuffer();
545       storeContentToStorage(fileLength, file, readOnly, bytes, bytes.length);
546       return nativeStream;
547     }
548     final BufferExposingByteArrayOutputStream cache = new BufferExposingByteArrayOutputStream((int)fileLength);
549     return new ReplicatorInputStream(nativeStream, cache) {
550       @Override
551       public void close() throws IOException {
552         super.close();
553         storeContentToStorage(fileLength, file, readOnly, cache.getInternalBuffer(), cache.size());
554       }
555     };
556   }
557
558   private void storeContentToStorage(long fileLength,
559                                      VirtualFile file,
560                                      boolean readOnly, byte[] bytes, int bytesLength)
561     throws IOException {
562     synchronized (INPUT_LOCK) {
563       if (bytesLength == fileLength) {
564         writeContent(file, new ByteSequence(bytes, 0, bytesLength), readOnly);
565         setFlag(file, MUST_RELOAD_CONTENT, false);
566       }
567       else {
568         setFlag(file, MUST_RELOAD_CONTENT, true);
569       }
570     }
571   }
572
573   private static boolean mustReloadContent(final VirtualFile file) {
574     return checkFlag(file, MUST_RELOAD_CONTENT) || FSRecords.getLength(getFileId(file)) == -1L;
575   }
576
577   @Override
578   @NotNull
579   public OutputStream getOutputStream(@NotNull final VirtualFile file, final Object requestor, final long modStamp, final long timeStamp)
580     throws IOException {
581     final VFileContentChangeEvent event = new VFileContentChangeEvent(requestor, file, file.getModificationStamp(), modStamp, false);
582
583     final List<VFileContentChangeEvent> events = Collections.singletonList(event);
584
585     final BulkFileListener publisher = myEventsBus.syncPublisher(VirtualFileManager.VFS_CHANGES);
586     publisher.before(events);
587
588     return new ByteArrayOutputStream() {
589       @Override
590       public void close() throws IOException {
591         super.close();
592
593         NewVirtualFileSystem delegate = getDelegate(file);
594         final OutputStream outputStream = delegate.getOutputStream(file, requestor, modStamp, timeStamp);
595
596         //noinspection IOResourceOpenedButNotSafelyClosed
597         final DupOutputStream sink = new DupOutputStream(new BufferedOutputStream(writeContent(file, delegate.isReadOnly())), outputStream) {
598           @Override
599           public void close() throws IOException {
600             try {
601               super.close();
602             }
603             finally {
604               executeTouch(file, false, event.getModificationStamp());
605               publisher.after(events);
606             }
607           }
608         };
609
610         try {
611           sink.write(buf, 0, count);
612         }
613         finally {
614           sink.close();
615         }
616       }
617     };
618   }
619
620   public int acquireContent(VirtualFile file) {
621     return FSRecords.acquireFileContent(getFileId(file));
622   }
623
624   public void releaseContent(int contentId) {
625     FSRecords.releaseContent(contentId);
626   }
627
628   public int getCurrentContentId(VirtualFile file) {
629     return FSRecords.getContentId(getFileId(file));
630   }
631
632   @Override
633   public void moveFile(final Object requestor, @NotNull final VirtualFile file, @NotNull final VirtualFile newParent) throws IOException {
634     getDelegate(file).moveFile(requestor, file, newParent);
635     processEvent(new VFileMoveEvent(requestor, file, newParent));
636   }
637
638   private void processEvent(VFileEvent event) {
639     processEvents(Collections.singletonList(event));
640   }
641
642   private static class EventWrapper {
643     private final VFileDeleteEvent event;
644     private final int id;
645
646     private EventWrapper(final VFileDeleteEvent event, final int id) {
647       this.event = event;
648       this.id = id;
649     }
650   }
651
652   private static final Comparator<EventWrapper> DEPTH_COMPARATOR = new Comparator<EventWrapper>() {
653     @Override
654     public int compare(final EventWrapper o1, final EventWrapper o2) {
655       return o1.event.getFileDepth() - o2.event.getFileDepth();
656     }
657   };
658
659   private static List<? extends VFileEvent> validateEvents(final List<? extends VFileEvent> events) {
660     final List<EventWrapper> deletionEvents = Lists.newArrayList();
661     for (int i = 0, size = events.size(); i < size; i++) {
662       final VFileEvent event = events.get(i);
663       if (event instanceof VFileDeleteEvent && event.isValid()) {
664         deletionEvents.add(new EventWrapper((VFileDeleteEvent)event, i));
665       }
666     }
667
668     ContainerUtil.quickSort(deletionEvents, DEPTH_COMPARATOR);
669
670     final TIntHashSet invalidIDs = new TIntHashSet(deletionEvents.size());
671     final List<VirtualFile> dirsToBeDeleted = new ArrayList<VirtualFile>();
672     nextEvent:
673     for (EventWrapper wrapper : deletionEvents) {
674       final VirtualFile candidate = wrapper.event.getFile();
675       for (VirtualFile file : dirsToBeDeleted) {
676         if (VfsUtilCore.isAncestor(file, candidate, false)) {
677           invalidIDs.add(wrapper.id);
678           continue nextEvent;
679         }
680       }
681
682       if (candidate.isDirectory()) {
683         dirsToBeDeleted.add(candidate);
684       }
685     }
686
687     final List<VFileEvent> filtered = Lists.newArrayListWithCapacity(events.size() - invalidIDs.size());
688     for (int i = 0, size = events.size(); i < size; i++) {
689       final VFileEvent event = events.get(i);
690       if (event.isValid() && !(event instanceof VFileDeleteEvent && invalidIDs.contains(i))) {
691         filtered.add(event);
692       }
693     }
694     return filtered;
695   }
696
697   @Override
698   public void processEvents(@NotNull List<? extends VFileEvent> events) {
699     ApplicationManager.getApplication().assertWriteAccessAllowed();
700
701     events = validateEvents(events);
702
703     BulkFileListener publisher = myEventsBus.syncPublisher(VirtualFileManager.VFS_CHANGES);
704     publisher.before(events);
705     for (VFileEvent event : events) {
706       applyEvent(event);
707     }
708     publisher.after(events);
709   }
710
711   public static final Object LOCK = new Object();
712
713   @Override
714   @Nullable
715   public VirtualFileSystemEntry findRoot(@NotNull final String basePath, @NotNull final NewVirtualFileSystem fs) { // TODO: read/write locks instead of synchronized
716     synchronized (LOCK) {
717       final String rootUrl = fs.getProtocol() + "://" + basePath;
718       VirtualFileSystemEntry root = myRoots.get(rootUrl);
719       if (root == null && basePath.length() == 0) {
720         root = myFakeRoot;
721       }
722       if (root == null) {
723         try {
724           final int rootId = FSRecords.findRootRecord(rootUrl);
725           if (basePath.length() > 0) {
726             root = new VirtualDirectoryImpl(basePath, null, fs, rootId) {
727               @NotNull
728               @Override
729               public String getName() {
730                 final String name = super.getName();
731
732                 // TODO: HACK!!! Get to simpler solution.
733                 if (fs instanceof JarFileSystem) {
734                   String jarName = name.substring(0, name.length() - JarFileSystem.JAR_SEPARATOR.length());
735                   return jarName.substring(jarName.lastIndexOf('/') + 1);
736                 }
737
738                 return name;
739               }
740             };
741           }
742           else {
743             // fake root for windows
744             root = new VirtualDirectoryImpl("", null, fs, rootId) {
745               @Override
746               @NotNull
747               public VirtualFile[] getChildren() {
748                 return getRoots(fs);
749               }
750
751               @Override
752               public VirtualFileSystemEntry findChild(@NotNull String name) {
753                 if (name.length() == 0) return null;
754                 return findRoot(name, fs);
755               }
756             };
757           }
758           if (!fs.exists(root)) return null;
759
760           boolean newRoot = copyRecordFromDelegateFS(rootId, 0, root, fs);
761           if (!newRoot) {
762             if (fs.getTimeStamp(root) != FSRecords.getTimestamp(rootId)) {
763               root.markDirtyRecursively();
764             }
765           }
766         }
767         catch (IOException e) {
768           throw new RuntimeException(e);
769         }
770
771         if (basePath.length() > 0) {
772           myRoots.put(rootUrl, root);
773         }
774         else {
775           myFakeRoot = root;
776         }
777       }
778
779       return root;
780     }
781   }
782
783   @Override
784   public void refresh(final boolean asynchronous) {
785     final NewVirtualFile[] roots;
786     synchronized (LOCK) {
787       Collection<VirtualFileSystemEntry> values = myRoots.values();
788       roots = values.toArray(new NewVirtualFile[values.size()]);
789     }
790
791     RefreshQueue.getInstance().refresh(asynchronous, true, null, roots);
792   }
793
794   @Override
795   public void refresh(boolean asynchronous, Runnable postAction, ModalityState modalityState) {
796     final NewVirtualFile[] roots;
797     synchronized (LOCK) {
798       Collection<VirtualFileSystemEntry> values = myRoots.values();
799       roots = values.toArray(new NewVirtualFile[values.size()]);
800     }
801
802     RefreshQueue.getInstance().refresh(asynchronous, true, postAction, modalityState, roots);
803   }
804
805   @Override
806   @NotNull
807   public VirtualFile[] getLocalRoots() {
808     List<NewVirtualFile> roots;
809     synchronized (LOCK) {
810       roots = new ArrayList<NewVirtualFile>(myRoots.values());
811
812       final Iterator<NewVirtualFile> it = roots.iterator();
813       while (it.hasNext()) {
814         NewVirtualFile file = it.next();
815         if (!file.isInLocalFileSystem()) {
816           it.remove();
817         }
818       }
819     }
820
821     return VfsUtil.toVirtualFileArray(roots);
822   }
823
824   //guarded by dirCacheReadLock/dirCacheWriteLock
825   private final StripedLockIntObjectConcurrentHashMap<NewVirtualFile> myIdToDirCache = new StripedLockIntObjectConcurrentHashMap<NewVirtualFile>();
826
827   public void clearIdCache() {
828     myIdToDirCache.clear();
829   }
830
831   @Override
832   @Nullable
833   public NewVirtualFile findFileById(final int id) {
834     return _findFileById(id, false);
835   }
836
837
838   @Nullable
839   public NewVirtualFile findFileByIdIfCached(final int id) {
840     return _findFileById(id, true);
841   }
842
843   @Nullable
844   private NewVirtualFile _findFileById(int id, final boolean cachedOnly) {
845     final NewVirtualFile cached = myIdToDirCache.get(id);
846     if (cached != null) {
847       return cached;
848     }
849
850     NewVirtualFile result = doFindFile(id, cachedOnly);
851
852     if (result != null && result.isDirectory()) {
853       NewVirtualFile old = myIdToDirCache.putIfAbsent(id, result);
854       if (old != null) result = old;
855     }
856     return result;
857   }
858
859   @Nullable
860   private NewVirtualFile doFindFile(final int id, boolean cachedOnly) {
861     final int parentId = getParent(id);
862     if (parentId == 0) {
863       synchronized (LOCK) {
864         for (NewVirtualFile root : myRoots.values()) {
865           if (root.getId() == id) return root;
866         }
867         return null;
868       }
869     }
870     else {
871       NewVirtualFile parentFile = _findFileById(parentId, cachedOnly);
872       if (parentFile == null) {
873         return null;
874       }
875       return cachedOnly ? parentFile.findChildByIdIfCached(id) : parentFile.findChildById(id);
876     }
877   }
878
879   @Override
880   @NotNull
881   public VirtualFile[] getRoots() {
882     synchronized (LOCK) {
883       Collection<VirtualFileSystemEntry> roots = myRoots.values();
884       return VfsUtil.toVirtualFileArray(roots);
885     }
886   }
887
888   @Override
889   @NotNull
890   public VirtualFile[] getRoots(@NotNull final NewVirtualFileSystem fs) {
891     List<VirtualFile> roots = new ArrayList<VirtualFile>();
892     synchronized (LOCK) {
893       for (NewVirtualFile root : myRoots.values()) {
894         if (root.getFileSystem() == fs) {
895           roots.add(root);
896         }
897       }
898     }
899
900     return VfsUtil.toVirtualFileArray(roots);
901   }
902
903   private void applyEvent(final VFileEvent event) {
904     try {
905       if (event instanceof VFileCreateEvent) {
906         final VFileCreateEvent createEvent = (VFileCreateEvent)event;
907         executeCreateChild(createEvent.getParent(), createEvent.getChildName());
908       }
909       else if (event instanceof VFileDeleteEvent) {
910         final VFileDeleteEvent deleteEvent = (VFileDeleteEvent)event;
911         executeDelete(deleteEvent.getFile());
912       }
913       else if (event instanceof VFileContentChangeEvent) {
914         final VFileContentChangeEvent contentUpdateEvent = (VFileContentChangeEvent)event;
915         executeTouch(contentUpdateEvent.getFile(), contentUpdateEvent.isFromRefresh(), contentUpdateEvent.getModificationStamp());
916       }
917       else if (event instanceof VFileCopyEvent) {
918         final VFileCopyEvent copyEvent = (VFileCopyEvent)event;
919         executeCopy(copyEvent.getFile(), copyEvent.getNewParent(), copyEvent.getNewChildName());
920       }
921       else if (event instanceof VFileMoveEvent) {
922         final VFileMoveEvent moveEvent = (VFileMoveEvent)event;
923         executeMove(moveEvent.getFile(), moveEvent.getNewParent());
924       }
925       else if (event instanceof VFilePropertyChangeEvent) {
926         final VFilePropertyChangeEvent propertyChangeEvent = (VFilePropertyChangeEvent)event;
927         if (VirtualFile.PROP_NAME.equals(propertyChangeEvent.getPropertyName())) {
928           executeRename(propertyChangeEvent.getFile(), (String)propertyChangeEvent.getNewValue());
929         }
930         else if (VirtualFile.PROP_WRITABLE.equals(propertyChangeEvent.getPropertyName())) {
931           executeSetWritable(propertyChangeEvent.getFile(), ((Boolean)propertyChangeEvent.getNewValue()).booleanValue());
932         }
933       }
934     }
935     catch (Exception e) {
936       // Exception applying single event should not prevent other events from applying.
937       LOG.error(e);
938     }
939   }
940
941   @NonNls
942   public String toString() {
943     return "PersistentFS";
944   }
945
946   private static void executeCreateChild(final VirtualFile parent, final String name) {
947     final NewVirtualFileSystem delegate = getDelegate(parent);
948     VirtualFile fakeFile = new FakeVirtualFile(parent, name);
949     if (delegate.exists(fakeFile)) {
950       final int parentId = getFileId(parent);
951       int childId = createAndCopyRecord(delegate, fakeFile, parentId);
952
953       appendIdToParentList(parentId, childId);
954       final VirtualDirectoryImpl dir = (VirtualDirectoryImpl)parent;
955       dir.addChild(dir.createChild(name, childId));
956     }
957   }
958
959   private static int createAndCopyRecord(NewVirtualFileSystem delegateSystem, VirtualFile delegateFile, int parentId) {
960     int childId = FSRecords.createRecord();
961     copyRecordFromDelegateFS(childId, parentId, delegateFile, delegateSystem);
962     return childId;
963   }
964
965   private static void appendIdToParentList(final int parentId, final int childId) {
966     int[] childrenList = FSRecords.list(parentId);
967     childrenList = ArrayUtil.append(childrenList, childId);
968     FSRecords.updateList(parentId, childrenList);
969   }
970
971   private void executeDelete(final VirtualFile file) {
972     if (!file.exists()) {
973       LOG.error("Deleting a file, which does not exist: " + file.getPath());
974     }
975     else {
976       clearIdCache();
977
978       final int id = getFileId(file);
979
980       final VirtualFile parent = file.getParent();
981       final int parentId = parent != null ? getFileId(parent) : 0;
982
983       FSRecords.deleteRecordRecursively(id);
984
985       if (parentId != 0) {
986         removeIdFromParentList(parentId, id, parent, file);
987         VirtualDirectoryImpl directory = (VirtualDirectoryImpl)file.getParent();
988         assert directory != null;
989
990         directory.removeChild(file);
991       }
992       else {
993         synchronized (LOCK) {
994           myRoots.remove(file.getUrl());
995           try {
996             FSRecords.deleteRootRecord(id);
997           }
998           catch (IOException e) {
999             throw new RuntimeException(e);
1000           }
1001         }
1002       }
1003
1004       invalidateSubtree(file);
1005     }
1006   }
1007
1008   private static void invalidateSubtree(final VirtualFile file) {
1009     final VirtualFileSystemEntry impl = (VirtualFileSystemEntry)file;
1010     impl.invalidate();
1011     for (VirtualFile child : impl.getCachedChildren()) {
1012       invalidateSubtree(child);
1013     }
1014   }
1015
1016   private static void removeIdFromParentList(final int parentId, final int id, VirtualFile parent, VirtualFile file) {
1017     int[] childList = FSRecords.list(parentId);
1018
1019     int index = ArrayUtil.indexOf(childList, id);
1020     if (index == -1) {
1021       throw new RuntimeException("Cannot find child (" + id + ")" + file
1022                                  + "\n\tin (" + parentId + ")" + parent
1023                                  + "\n\tactual children:" + Arrays.toString(childList));
1024     }
1025     childList = ArrayUtil.remove(childList, index);
1026     FSRecords.updateList(parentId, childList);
1027   }
1028
1029   private static void executeRename(final VirtualFile file, final String newName) {
1030     ((VirtualFileSystemEntry)file).setName(newName);
1031     final int id = getFileId(file);
1032     FSRecords.setName(id, newName);
1033   }
1034
1035   private static void executeSetWritable(final VirtualFile file, final boolean writableFlag) {
1036     setFlag(file, IS_READ_ONLY, !writableFlag);
1037   }
1038
1039   private static void setFlag(VirtualFile file, int mask, boolean value) {
1040     setFlag(getFileId(file), mask, value);
1041   }
1042
1043   private static void setFlag(final int id, final int mask, final boolean value) {
1044     int oldFlags = FSRecords.getFlags(id);
1045     int flags = value ? oldFlags | mask : oldFlags & ~mask;
1046
1047     if (oldFlags != flags) {
1048       FSRecords.setFlags(id, flags, true);
1049     }
1050   }
1051
1052   private static boolean checkFlag(VirtualFile file, int mask) {
1053     return (FSRecords.getFlags(getFileId(file)) & mask) != 0;
1054   }
1055
1056   private static void executeTouch(final VirtualFile file, boolean reloadContentFromDelegate, long newModificationStamp) {
1057     if (reloadContentFromDelegate) {
1058       setFlag(file, MUST_RELOAD_CONTENT, true);
1059     }
1060
1061     final NewVirtualFileSystem delegate = getDelegate(file);
1062     FSRecords.setLength(getFileId(file), delegate.getLength(file));
1063     FSRecords.setTimestamp(getFileId(file), delegate.getTimeStamp(file));
1064
1065     ((NewVirtualFile)file).setModificationStamp(newModificationStamp);
1066   }
1067
1068   @SuppressWarnings({"UnusedDeclaration"})
1069   private static void executeCopy(final VirtualFile from, final VirtualFile newParent, final String copyName) {
1070     executeCreateChild(newParent, copyName);
1071   }
1072
1073   private static void executeMove(final VirtualFile file, final VirtualFile newParent) {
1074     final int fileId = getFileId(file);
1075     final int newParentId = getFileId(newParent);
1076     final int oldParentId = getFileId(file.getParent());
1077
1078     removeIdFromParentList(oldParentId, fileId, file.getParent(), file);
1079     appendIdToParentList(newParentId, fileId);
1080
1081     ((VirtualFileSystemEntry)file).setParent(newParent);
1082     FSRecords.setParent(fileId, newParentId);
1083   }
1084
1085   public String getName(final int id) {
1086     assert id > 0;
1087     return FSRecords.getName(id);
1088   }
1089
1090   public void cleanPersistedContents() {
1091     try {
1092       final int[] roots = FSRecords.listRoots();
1093       for (int root : roots) {
1094         cleanPersistedContentsRecursively(root);
1095       }
1096     }
1097     catch (IOException e) {
1098       throw new RuntimeException(e);
1099     }
1100   }
1101
1102   private static void cleanPersistedContentsRecursively(int id) {
1103     if (isDirectory(id)) {
1104       for (int child : FSRecords.list(id)) {
1105         cleanPersistedContentsRecursively(child);
1106       }
1107     }
1108     else {
1109       setFlag(id, MUST_RELOAD_CONTENT, true);
1110     }
1111   }
1112
1113   public static NewVirtualFileSystem replaceWithNativeFS(NewVirtualFileSystem delegate) {
1114     if (delegate.getProtocol().equals(LocalFileSystem.PROTOCOL) && Registry.is("filesystem.useNative")) {
1115         if (SystemInfo.isWindows && Win32LocalFileSystem.isAvailable()) {
1116           delegate = Win32LocalFileSystem.getWin32Instance();
1117         }
1118       }
1119     return delegate;
1120   }
1121 }