2 * Copyright 2000-2011 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.openapi.vfs.newvfs.persistent;
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.THashMap;
46 import gnu.trove.TIntHashSet;
47 import gnu.trove.TIntObjectHashMap;
48 import org.jetbrains.annotations.NonNls;
49 import org.jetbrains.annotations.NotNull;
50 import org.jetbrains.annotations.Nullable;
54 import java.util.concurrent.atomic.AtomicBoolean;
59 public class PersistentFS extends ManagingFS implements ApplicationComponent {
60 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.newvfs.persistent.PersistentFS");
62 private static final int CHILDREN_CACHED_FLAG = 0x01;
63 static final int IS_DIRECTORY_FLAG = 0x02;
64 private static final int IS_READ_ONLY = 0x04;
65 private static final int MUST_RELOAD_CONTENT = 0x08;
66 private static final int IS_SYMLINK = 0x10;
67 private static final int IS_SPECIAL = 0x20;
69 static final int ALL_VALID_FLAGS =
70 CHILDREN_CACHED_FLAG | IS_DIRECTORY_FLAG | IS_READ_ONLY | MUST_RELOAD_CONTENT | IS_SYMLINK | IS_SPECIAL;
72 private final MessageBus myEventsBus;
74 private final Map<String, VirtualFileSystemEntry> myRoots = new THashMap<String, VirtualFileSystemEntry>();
75 private final TIntObjectHashMap<VirtualFileSystemEntry> myRootsById = new TIntObjectHashMap<VirtualFileSystemEntry>();
76 private VirtualFileSystemEntry myFakeRoot;
77 private final Object INPUT_LOCK = new Object();
79 public PersistentFS(MessageBus bus) {
82 ShutDownTracker.getInstance().registerShutdownTask(new Runnable() {
91 public void disposeComponent() {
95 private final AtomicBoolean myShutdownPerformed = new AtomicBoolean(Boolean.FALSE);
96 private void performShutdown() {
97 if (!myShutdownPerformed.getAndSet(Boolean.TRUE)) {
98 LOG.info("VFS dispose started");
100 LOG.info("VFS dispose completed");
107 public String getComponentName() {
108 return "app.component.PersistentFS";
112 public void initComponent() {
117 public boolean areChildrenLoaded(@NotNull final VirtualFile dir) {
118 return areChildrenLoaded(getFileId(dir));
121 public long getCreationTimestamp() {
122 return FSRecords.getCreationTimestamp();
125 private static NewVirtualFileSystem getDelegate(VirtualFile file) {
126 return (NewVirtualFileSystem)file.getFileSystem();
130 public boolean wereChildrenAccessed(@NotNull final VirtualFile dir) {
131 return FSRecords.wereChildrenAccessed(getFileId(dir));
136 public String[] list(@NotNull final VirtualFile file) {
137 int id = getFileId(file);
138 int[] childrenIds = FSRecords.list(id);
139 String[] names = listPersisted(childrenIds);
140 if (areChildrenLoaded(id)) {
143 Pair<String[], int[]> pair = persistAllChildren(file, id, Pair.create(names, childrenIds));
148 public static String[] listPersisted(@NotNull VirtualFile file) {
149 return listPersisted(FSRecords.list(getFileId(file)));
153 private static String[] listPersisted(@NotNull int[] childrenIds) {
154 String[] names = ArrayUtil.newStringArray(childrenIds.length);
155 for (int i = 0; i < childrenIds.length; i++) {
156 names[i] = FSRecords.getName(childrenIds[i]);
162 private static Pair<String[],int[]> persistAllChildren(@NotNull VirtualFile file, int id, @NotNull Pair<String[],int[]> current) {
163 String[] currentNames = current.first;
164 int[] currentIds = current.second;
166 final NewVirtualFileSystem delegate = getDelegate(file);
167 String[] delegateNames = VfsUtil.filterNames(delegate.list(file));
168 if (delegateNames.length == 0 && currentNames.length > 0) {
172 final String[] names;
173 if (currentNames.length == 0) {
174 names = delegateNames;
177 Set<String> allNamesSet = new LinkedHashSet<String>((currentNames.length + delegateNames.length) * 2);
178 ContainerUtil.addAll(allNamesSet, currentNames);
179 ContainerUtil.addAll(allNamesSet, delegateNames);
180 names = ArrayUtil.toStringArray(allNamesSet);
183 final int[] childrenIds = ArrayUtil.newIntArray(names.length);
185 for (int i = 0; i < names.length; i++) {
186 final String name = names[i];
187 int idx = ArrayUtil.indexOf(currentNames, name);
189 childrenIds[i] = currentIds[idx];
192 int childId = createAndCopyRecord(delegate, new FakeVirtualFile(file, name), id);
193 childrenIds[i] = childId;
197 FSRecords.updateList(id, childrenIds);
198 int flags = FSRecords.getFlags(id);
199 FSRecords.setFlags(id, flags | CHILDREN_CACHED_FLAG, true);
201 return Pair.create(names, childrenIds);
205 public static int[] listIds(@NotNull VirtualFile parent) {
206 final int parentId = getFileId(parent);
208 int[] ids = FSRecords.list(parentId);
209 if (!areChildrenLoaded(parentId)) {
210 String[] names = listPersisted(ids);
211 Pair<String[], int[]> pair = persistAllChildren(parent, parentId, Pair.create(names, ids));
219 public static Pair<String[],int[]> listAll(@NotNull VirtualFile parent) {
220 final int parentId = getFileId(parent);
222 Pair<String[], int[]> pair = FSRecords.listAll(parentId);
223 if (!areChildrenLoaded(parentId)) {
224 return persistAllChildren(parent, parentId, pair);
231 private static boolean areChildrenLoaded(final int parentId) {
232 final int mask = CHILDREN_CACHED_FLAG;
233 return (FSRecords.getFlags(parentId) & mask) != 0;
238 public DataInputStream readAttribute(@NotNull final VirtualFile file, @NotNull final FileAttribute att) {
239 return FSRecords.readAttribute(getFileId(file), att.getId());
244 public DataOutputStream writeAttribute(@NotNull final VirtualFile file, @NotNull final FileAttribute att) {
245 return FSRecords.writeAttribute(getFileId(file), att.getId(), att.isFixedSize());
249 private static DataInputStream readContent(VirtualFile file) {
250 return FSRecords.readContent(getFileId(file));
254 private static DataInputStream readContentById(int contentId) {
255 return FSRecords.readContentById(contentId);
258 private static DataOutputStream writeContent(VirtualFile file, boolean readOnly) {
259 return FSRecords.writeContent(getFileId(file), readOnly);
262 private static void writeContent(VirtualFile file, ByteSequence content, boolean readOnly) throws IOException {
263 FSRecords.writeContent(getFileId(file), content, readOnly);
266 public int storeUnlinkedContent(byte[] bytes) {
267 return FSRecords.storeUnlinkedContent(bytes);
271 public int getModificationCount(@NotNull final VirtualFile file) {
272 final int id = getFileId(file);
273 return FSRecords.getModCount(id);
277 public int getCheapFileSystemModificationCount() {
278 return FSRecords.getLocalModCount();
282 public int getFilesystemModificationCount() {
283 return FSRecords.getModCount();
286 private static boolean copyRecordFromDelegateFS(final int id, final int parentId, final VirtualFile file, NewVirtualFileSystem delegate) {
287 String name = file.getName();
289 if (name.length() > 0 && namesEqual(delegate, name, FSRecords.getName(id))) return false; // TODO: Handle root attributes change.
291 if (name.length() == 0) { // TODO: hack
292 if (areChildrenLoaded(id)) return false;
295 FSRecords.setParent(id, parentId);
296 FSRecords.setName(id, name);
298 delegate = replaceWithNativeFS(delegate);
300 FSRecords.setTimestamp(id, delegate.getTimeStamp(file));
302 boolean directory = delegate.isDirectory(file);
304 FSRecords.setLength(id, directory ? -1L : delegate.getLength(file));
306 FSRecords.setFlags(id, (directory ? IS_DIRECTORY_FLAG : 0) |
307 (delegate.isWritable(file) ? 0 : IS_READ_ONLY) |
308 (delegate.isSymLink(file) ? IS_SYMLINK : 0) |
309 (delegate.isSpecialFile(file) ? IS_SPECIAL : 0), true);
315 public boolean isDirectory(@NotNull final VirtualFile file) {
316 final int id = getFileId(file);
317 return isDirectory(id);
320 public static boolean isDirectory(final int id) {
322 return (FSRecords.getFlags(id) & IS_DIRECTORY_FLAG) != 0;
325 public static boolean isSymLink(final int id) {
327 return (FSRecords.getFlags(id) & IS_SYMLINK) != 0;
330 private static int getParent(final int id) {
332 return FSRecords.getParent(id);
335 private static boolean namesEqual(VirtualFileSystem fs, String n1, String n2) {
336 return ((NewVirtualFileSystem)fs).isCaseSensitive() ? n1.equals(n2) : n1.equalsIgnoreCase(n2);
340 public boolean exists(@NotNull final VirtualFile fileOrDirectory) {
341 return ((VirtualFileWithId)fileOrDirectory).getId() > 0;
345 public long getTimeStamp(@NotNull final VirtualFile file) {
346 final int id = getFileId(file);
347 return FSRecords.getTimestamp(id);
351 public void setTimeStamp(@NotNull final VirtualFile file, final long modstamp) throws IOException {
352 final int id = getFileId(file);
354 FSRecords.setTimestamp(id, modstamp);
355 getDelegate(file).setTimeStamp(file, modstamp);
358 private static int getFileId(final VirtualFile file) {
359 final int id = ((VirtualFileWithId)file).getId();
361 throw new InvalidVirtualFileAccessException(file);
367 public boolean isSymLink(@NotNull VirtualFile file) {
368 return (FSRecords.getFlags(getFileId(file)) & IS_SYMLINK) != 0;
372 public String resolveSymLink(@NotNull VirtualFile file) {
373 throw new UnsupportedOperationException();
377 public boolean isSpecialFile(@NotNull VirtualFile file) {
378 return (FSRecords.getFlags(getFileId(file)) & IS_SPECIAL) != 0;
382 public boolean isWritable(@NotNull final VirtualFile file) {
383 return (FSRecords.getFlags(getFileId(file)) & IS_READ_ONLY) == 0;
387 public void setWritable(@NotNull final VirtualFile file, final boolean writableFlag) throws IOException {
388 getDelegate(file).setWritable(file, writableFlag);
389 processEvent(new VFilePropertyChangeEvent(this, file, VirtualFile.PROP_WRITABLE, isWritable(file), writableFlag, false));
392 public static int getId(final VirtualFile parent, final String childName, NewVirtualFileSystem delegate) {
393 final int parentId = getFileId(parent);
395 final int[] children = FSRecords.list(parentId);
396 for (final int childId : children) {
397 if (namesEqual(delegate, childName, FSRecords.getName(childId))) return childId;
400 VirtualFile fake = new FakeVirtualFile(parent, childName);
401 if (delegate.exists(fake)) {
402 int child = createAndCopyRecord(delegate, fake, parentId);
403 FSRecords.updateList(parentId, ArrayUtil.append(children, child));
411 public long getLength(@NotNull final VirtualFile file) {
412 final int id = getFileId(file);
414 long len = FSRecords.getLength(id);
416 len = (int)getDelegate(file).getLength(file);
417 FSRecords.setLength(id, len);
424 public VirtualFile copyFile(final Object requestor, @NotNull final VirtualFile file, @NotNull final VirtualFile newParent, @NotNull final String copyName)
426 getDelegate(file).copyFile(requestor, file, newParent, copyName);
427 processEvent(new VFileCopyEvent(requestor, file, newParent, copyName));
429 final VirtualFile child = newParent.findChild(copyName);
431 throw new IOException("Cannot create child");
437 public VirtualFile createChildDirectory(final Object requestor, @NotNull final VirtualFile parent, @NotNull final String dir) throws IOException {
438 getDelegate(parent).createChildDirectory(requestor, parent, dir);
439 processEvent(new VFileCreateEvent(requestor, parent, dir, true, false));
441 final VirtualFile child = parent.findChild(dir);
443 throw new IOException("Cannot create child directory '" + dir + "' at " + parent.getPath());
449 public VirtualFile createChildFile(final Object requestor, @NotNull final VirtualFile parent, @NotNull final String file) throws IOException {
450 getDelegate(parent).createChildFile(requestor, parent, file);
451 processEvent(new VFileCreateEvent(requestor, parent, file, false, false));
453 final VirtualFile child = parent.findChild(file);
455 throw new IOException("Cannot create child file '" + file + "' at " + parent.getPath());
461 public void deleteFile(final Object requestor, @NotNull final VirtualFile file) throws IOException {
462 final NewVirtualFileSystem delegate = getDelegate(file);
463 delegate.deleteFile(requestor, file);
465 if (!delegate.exists(file)) {
466 processEvent(new VFileDeleteEvent(requestor, file, false));
471 public void renameFile(final Object requestor, @NotNull final VirtualFile file, @NotNull final String newName) throws IOException {
472 getDelegate(file).renameFile(requestor, file, newName);
473 processEvent(new VFilePropertyChangeEvent(requestor, file, VirtualFile.PROP_NAME, file.getName(), newName, false));
478 public byte[] contentsToByteArray(@NotNull final VirtualFile file) throws IOException {
479 return contentsToByteArray(file, true);
483 public byte[] contentsToByteArray(final VirtualFile file, boolean cacheContent) throws IOException {
484 InputStream contentStream = null;
485 boolean reloadFromDelegate;
486 synchronized (INPUT_LOCK) {
487 reloadFromDelegate = mustReloadContent(file) || (contentStream = readContent(file)) == null;
490 if (reloadFromDelegate) {
491 final NewVirtualFileSystem delegate = getDelegate(file);
492 FSRecords.setLength(getFileId(file), delegate.getLength(file));
493 final byte[] content = delegate.contentsToByteArray(file);
495 ApplicationEx application = (ApplicationEx)ApplicationManager.getApplication();
496 // we should cache every local files content
497 // because the local history feature is currently depends on this cache,
498 // perforce offline mode as well
499 if ((!delegate.isReadOnly() || !application.isInternal() && !application.isUnitTestMode()) &&
500 content.length <= PersistentFSConstants.FILE_LENGTH_TO_CACHE_THRESHOLD) {
501 synchronized (INPUT_LOCK) {
502 writeContent(file, new ByteSequence(content), delegate.isReadOnly());
503 setFlag(file, MUST_RELOAD_CONTENT, false);
511 return FileUtil.loadBytes(contentStream, (int)file.getLength());
513 catch (IOException e) {
514 throw FSRecords.handleError(e);
519 public byte[] contentsToByteArray(int contentId) throws IOException {
520 return FileUtil.loadBytes(readContentById(contentId));
525 public InputStream getInputStream(@NotNull final VirtualFile file) throws IOException {
526 synchronized (INPUT_LOCK) {
527 InputStream contentStream;
528 if (mustReloadContent(file) || (contentStream = readContent(file)) == null) {
529 final NewVirtualFileSystem delegate = getDelegate(file);
530 final long len = delegate.getLength(file);
531 FSRecords.setLength(getFileId(file), len);
532 final InputStream nativeStream = delegate.getInputStream(file);
534 if (len > PersistentFSConstants.FILE_LENGTH_TO_CACHE_THRESHOLD) return nativeStream;
535 return createReplicator(file, nativeStream, len, delegate.isReadOnly());
538 return contentStream;
543 private InputStream createReplicator(final VirtualFile file, final InputStream nativeStream, final long fileLength, final boolean readOnly)
545 if (nativeStream instanceof BufferExposingByteArrayInputStream) {
547 BufferExposingByteArrayInputStream byteStream = (BufferExposingByteArrayInputStream )nativeStream;
548 byte[] bytes = byteStream.getInternalBuffer();
549 storeContentToStorage(fileLength, file, readOnly, bytes, bytes.length);
552 final BufferExposingByteArrayOutputStream cache = new BufferExposingByteArrayOutputStream((int)fileLength);
553 return new ReplicatorInputStream(nativeStream, cache) {
555 public void close() throws IOException {
557 storeContentToStorage(fileLength, file, readOnly, cache.getInternalBuffer(), cache.size());
562 private void storeContentToStorage(long fileLength,
564 boolean readOnly, byte[] bytes, int bytesLength)
566 synchronized (INPUT_LOCK) {
567 if (bytesLength == fileLength) {
568 writeContent(file, new ByteSequence(bytes, 0, bytesLength), readOnly);
569 setFlag(file, MUST_RELOAD_CONTENT, false);
572 setFlag(file, MUST_RELOAD_CONTENT, true);
577 private static boolean mustReloadContent(final VirtualFile file) {
578 return checkFlag(file, MUST_RELOAD_CONTENT) || FSRecords.getLength(getFileId(file)) == -1L;
583 public OutputStream getOutputStream(@NotNull final VirtualFile file, final Object requestor, final long modStamp, final long timeStamp)
585 final VFileContentChangeEvent event = new VFileContentChangeEvent(requestor, file, file.getModificationStamp(), modStamp, false);
587 final List<VFileContentChangeEvent> events = Collections.singletonList(event);
589 final BulkFileListener publisher = myEventsBus.syncPublisher(VirtualFileManager.VFS_CHANGES);
590 publisher.before(events);
592 return new ByteArrayOutputStream() {
594 public void close() throws IOException {
597 NewVirtualFileSystem delegate = getDelegate(file);
598 final OutputStream outputStream = delegate.getOutputStream(file, requestor, modStamp, timeStamp);
600 //noinspection IOResourceOpenedButNotSafelyClosed
601 final DupOutputStream sink = new DupOutputStream(new BufferedOutputStream(writeContent(file, delegate.isReadOnly())), outputStream) {
603 public void close() throws IOException {
608 executeTouch(file, false, event.getModificationStamp());
609 publisher.after(events);
615 sink.write(buf, 0, count);
624 public int acquireContent(VirtualFile file) {
625 return FSRecords.acquireFileContent(getFileId(file));
628 public void releaseContent(int contentId) {
629 FSRecords.releaseContent(contentId);
632 public int getCurrentContentId(VirtualFile file) {
633 return FSRecords.getContentId(getFileId(file));
637 public void moveFile(final Object requestor, @NotNull final VirtualFile file, @NotNull final VirtualFile newParent) throws IOException {
638 getDelegate(file).moveFile(requestor, file, newParent);
639 processEvent(new VFileMoveEvent(requestor, file, newParent));
642 private void processEvent(VFileEvent event) {
643 processEvents(Collections.singletonList(event));
646 private static class EventWrapper {
647 private final VFileDeleteEvent event;
648 private final int id;
650 private EventWrapper(final VFileDeleteEvent event, final int id) {
656 private static final Comparator<EventWrapper> DEPTH_COMPARATOR = new Comparator<EventWrapper>() {
658 public int compare(final EventWrapper o1, final EventWrapper o2) {
659 return o1.event.getFileDepth() - o2.event.getFileDepth();
663 private static List<? extends VFileEvent> validateEvents(final List<? extends VFileEvent> events) {
664 final List<EventWrapper> deletionEvents = Lists.newArrayList();
665 for (int i = 0, size = events.size(); i < size; i++) {
666 final VFileEvent event = events.get(i);
667 if (event instanceof VFileDeleteEvent && event.isValid()) {
668 deletionEvents.add(new EventWrapper((VFileDeleteEvent)event, i));
672 ContainerUtil.quickSort(deletionEvents, DEPTH_COMPARATOR);
674 final TIntHashSet invalidIDs = new TIntHashSet(deletionEvents.size());
675 final List<VirtualFile> dirsToBeDeleted = new ArrayList<VirtualFile>();
677 for (EventWrapper wrapper : deletionEvents) {
678 final VirtualFile candidate = wrapper.event.getFile();
679 for (VirtualFile file : dirsToBeDeleted) {
680 if (VfsUtilCore.isAncestor(file, candidate, false)) {
681 invalidIDs.add(wrapper.id);
686 if (candidate.isDirectory()) {
687 dirsToBeDeleted.add(candidate);
691 final List<VFileEvent> filtered = Lists.newArrayListWithCapacity(events.size() - invalidIDs.size());
692 for (int i = 0, size = events.size(); i < size; i++) {
693 final VFileEvent event = events.get(i);
694 if (event.isValid() && !(event instanceof VFileDeleteEvent && invalidIDs.contains(i))) {
702 public void processEvents(@NotNull List<? extends VFileEvent> events) {
703 ApplicationManager.getApplication().assertWriteAccessAllowed();
705 events = validateEvents(events);
707 BulkFileListener publisher = myEventsBus.syncPublisher(VirtualFileManager.VFS_CHANGES);
708 publisher.before(events);
709 for (VFileEvent event : events) {
712 publisher.after(events);
715 public static final Object LOCK = new Object();
719 public VirtualFileSystemEntry findRoot(@NotNull final String basePath, @NotNull final NewVirtualFileSystem fs) { // TODO: read/write locks instead of synchronized
720 synchronized (LOCK) {
721 final String rootUrl = fs.getProtocol() + "://" + basePath;
722 VirtualFileSystemEntry root = myRoots.get(rootUrl);
723 if (root == null && basePath.length() == 0) {
728 final int rootId = FSRecords.findRootRecord(rootUrl);
729 if (basePath.length() > 0) {
730 root = new VirtualDirectoryImpl(basePath, null, fs, rootId) {
733 public String getName() {
734 final String name = super.getName();
736 // TODO: HACK!!! Get to simpler solution.
737 if (fs instanceof JarFileSystem) {
738 String jarName = name.substring(0, name.length() - JarFileSystem.JAR_SEPARATOR.length());
739 return jarName.substring(jarName.lastIndexOf('/') + 1);
747 // fake root for windows
748 root = new VirtualDirectoryImpl("", null, fs, rootId) {
751 public VirtualFile[] getChildren() {
756 public VirtualFileSystemEntry findChild(@NotNull String name) {
757 if (name.length() == 0) return null;
758 return findRoot(name, fs);
762 if (!fs.exists(root)) return null;
764 boolean newRoot = copyRecordFromDelegateFS(rootId, 0, root, fs);
766 if (fs.getTimeStamp(root) != FSRecords.getTimestamp(rootId)) {
767 root.markDirtyRecursively();
771 catch (IOException e) {
772 throw new RuntimeException(e);
775 if (basePath.length() > 0) {
776 myRoots.put(rootUrl, root);
777 myRootsById.put(root.getId(), root);
789 public void refresh(final boolean asynchronous) {
790 final NewVirtualFile[] roots;
791 synchronized (LOCK) {
792 Collection<VirtualFileSystemEntry> values = myRoots.values();
793 roots = values.toArray(new NewVirtualFile[values.size()]);
796 RefreshQueue.getInstance().refresh(asynchronous, true, null, roots);
800 public void refresh(boolean asynchronous, Runnable postAction, ModalityState modalityState) {
801 final NewVirtualFile[] roots;
802 synchronized (LOCK) {
803 Collection<VirtualFileSystemEntry> values = myRoots.values();
804 roots = values.toArray(new NewVirtualFile[values.size()]);
807 RefreshQueue.getInstance().refresh(asynchronous, true, postAction, modalityState, roots);
812 public VirtualFile[] getLocalRoots() {
813 List<NewVirtualFile> roots;
814 synchronized (LOCK) {
815 roots = new ArrayList<NewVirtualFile>(myRoots.values());
817 final Iterator<NewVirtualFile> it = roots.iterator();
818 while (it.hasNext()) {
819 NewVirtualFile file = it.next();
820 if (!file.isInLocalFileSystem()) {
826 return VfsUtil.toVirtualFileArray(roots);
829 //guarded by dirCacheReadLock/dirCacheWriteLock
830 private final StripedLockIntObjectConcurrentHashMap<NewVirtualFile> myIdToDirCache = new StripedLockIntObjectConcurrentHashMap<NewVirtualFile>();
832 public void clearIdCache() {
833 myIdToDirCache.clear();
838 public NewVirtualFile findFileById(final int id) {
839 return _findFileById(id, false);
844 public NewVirtualFile findFileByIdIfCached(final int id) {
845 return _findFileById(id, true);
849 private NewVirtualFile _findFileById(int id, final boolean cachedOnly) {
850 final NewVirtualFile cached = myIdToDirCache.get(id);
851 if (cached != null) {
855 NewVirtualFile result = doFindFile(id, cachedOnly);
857 if (result != null && result.isDirectory()) {
858 NewVirtualFile old = myIdToDirCache.putIfAbsent(id, result);
859 if (old != null) result = old;
865 private NewVirtualFile doFindFile(final int id, boolean cachedOnly) {
866 final int parentId = getParent(id);
868 synchronized (LOCK) {
869 return myRootsById.get(id);
873 NewVirtualFile parentFile = _findFileById(parentId, cachedOnly);
874 if (parentFile == null) {
877 return cachedOnly ? parentFile.findChildByIdIfCached(id) : parentFile.findChildById(id);
883 public VirtualFile[] getRoots() {
884 synchronized (LOCK) {
885 Collection<VirtualFileSystemEntry> roots = myRoots.values();
886 return VfsUtil.toVirtualFileArray(roots);
892 public VirtualFile[] getRoots(@NotNull final NewVirtualFileSystem fs) {
893 List<VirtualFile> roots = new ArrayList<VirtualFile>();
894 synchronized (LOCK) {
895 for (NewVirtualFile root : myRoots.values()) {
896 if (root.getFileSystem() == fs) {
902 return VfsUtil.toVirtualFileArray(roots);
905 private void applyEvent(final VFileEvent event) {
907 if (event instanceof VFileCreateEvent) {
908 final VFileCreateEvent createEvent = (VFileCreateEvent)event;
909 executeCreateChild(createEvent.getParent(), createEvent.getChildName());
911 else if (event instanceof VFileDeleteEvent) {
912 final VFileDeleteEvent deleteEvent = (VFileDeleteEvent)event;
913 executeDelete(deleteEvent.getFile());
915 else if (event instanceof VFileContentChangeEvent) {
916 final VFileContentChangeEvent contentUpdateEvent = (VFileContentChangeEvent)event;
917 executeTouch(contentUpdateEvent.getFile(), contentUpdateEvent.isFromRefresh(), contentUpdateEvent.getModificationStamp());
919 else if (event instanceof VFileCopyEvent) {
920 final VFileCopyEvent copyEvent = (VFileCopyEvent)event;
921 executeCopy(copyEvent.getFile(), copyEvent.getNewParent(), copyEvent.getNewChildName());
923 else if (event instanceof VFileMoveEvent) {
924 final VFileMoveEvent moveEvent = (VFileMoveEvent)event;
925 executeMove(moveEvent.getFile(), moveEvent.getNewParent());
927 else if (event instanceof VFilePropertyChangeEvent) {
928 final VFilePropertyChangeEvent propertyChangeEvent = (VFilePropertyChangeEvent)event;
929 if (VirtualFile.PROP_NAME.equals(propertyChangeEvent.getPropertyName())) {
930 executeRename(propertyChangeEvent.getFile(), (String)propertyChangeEvent.getNewValue());
932 else if (VirtualFile.PROP_WRITABLE.equals(propertyChangeEvent.getPropertyName())) {
933 executeSetWritable(propertyChangeEvent.getFile(), ((Boolean)propertyChangeEvent.getNewValue()).booleanValue());
937 catch (Exception e) {
938 // Exception applying single event should not prevent other events from applying.
944 public String toString() {
945 return "PersistentFS";
948 private static void executeCreateChild(final VirtualFile parent, final String name) {
949 final NewVirtualFileSystem delegate = getDelegate(parent);
950 VirtualFile fakeFile = new FakeVirtualFile(parent, name);
951 if (delegate.exists(fakeFile)) {
952 final int parentId = getFileId(parent);
953 int childId = createAndCopyRecord(delegate, fakeFile, parentId);
955 appendIdToParentList(parentId, childId);
956 final VirtualDirectoryImpl dir = (VirtualDirectoryImpl)parent;
957 dir.addChild(dir.createChild(name, childId));
961 private static int createAndCopyRecord(NewVirtualFileSystem delegateSystem, VirtualFile delegateFile, int parentId) {
962 int childId = FSRecords.createRecord();
963 copyRecordFromDelegateFS(childId, parentId, delegateFile, delegateSystem);
967 private static void appendIdToParentList(final int parentId, final int childId) {
968 int[] childrenList = FSRecords.list(parentId);
969 childrenList = ArrayUtil.append(childrenList, childId);
970 FSRecords.updateList(parentId, childrenList);
973 private void executeDelete(final VirtualFile file) {
974 if (!file.exists()) {
975 LOG.error("Deleting a file, which does not exist: " + file.getPath());
980 final int id = getFileId(file);
982 final VirtualFile parent = file.getParent();
983 final int parentId = parent != null ? getFileId(parent) : 0;
985 FSRecords.deleteRecordRecursively(id);
988 removeIdFromParentList(parentId, id, parent, file);
989 VirtualDirectoryImpl directory = (VirtualDirectoryImpl)file.getParent();
990 assert directory != null;
992 directory.removeChild(file);
995 synchronized (LOCK) {
996 myRoots.remove(file.getUrl());
997 myRootsById.remove(id);
999 FSRecords.deleteRootRecord(id);
1001 catch (IOException e) {
1002 throw new RuntimeException(e);
1007 invalidateSubtree(file);
1011 private static void invalidateSubtree(final VirtualFile file) {
1012 final VirtualFileSystemEntry impl = (VirtualFileSystemEntry)file;
1014 for (VirtualFile child : impl.getCachedChildren()) {
1015 invalidateSubtree(child);
1019 private static void removeIdFromParentList(final int parentId, final int id, VirtualFile parent, VirtualFile file) {
1020 int[] childList = FSRecords.list(parentId);
1022 int index = ArrayUtil.indexOf(childList, id);
1024 throw new RuntimeException("Cannot find child (" + id + ")" + file
1025 + "\n\tin (" + parentId + ")" + parent
1026 + "\n\tactual children:" + Arrays.toString(childList));
1028 childList = ArrayUtil.remove(childList, index);
1029 FSRecords.updateList(parentId, childList);
1032 private static void executeRename(final VirtualFile file, final String newName) {
1033 ((VirtualFileSystemEntry)file).setName(newName);
1034 final int id = getFileId(file);
1035 FSRecords.setName(id, newName);
1038 private static void executeSetWritable(final VirtualFile file, final boolean writableFlag) {
1039 setFlag(file, IS_READ_ONLY, !writableFlag);
1042 private static void setFlag(VirtualFile file, int mask, boolean value) {
1043 setFlag(getFileId(file), mask, value);
1046 private static void setFlag(final int id, final int mask, final boolean value) {
1047 int oldFlags = FSRecords.getFlags(id);
1048 int flags = value ? oldFlags | mask : oldFlags & ~mask;
1050 if (oldFlags != flags) {
1051 FSRecords.setFlags(id, flags, true);
1055 private static boolean checkFlag(VirtualFile file, int mask) {
1056 return (FSRecords.getFlags(getFileId(file)) & mask) != 0;
1059 private static void executeTouch(final VirtualFile file, boolean reloadContentFromDelegate, long newModificationStamp) {
1060 if (reloadContentFromDelegate) {
1061 setFlag(file, MUST_RELOAD_CONTENT, true);
1064 final NewVirtualFileSystem delegate = getDelegate(file);
1065 FSRecords.setLength(getFileId(file), delegate.getLength(file));
1066 FSRecords.setTimestamp(getFileId(file), delegate.getTimeStamp(file));
1068 ((NewVirtualFile)file).setModificationStamp(newModificationStamp);
1071 @SuppressWarnings({"UnusedDeclaration"})
1072 private static void executeCopy(final VirtualFile from, final VirtualFile newParent, final String copyName) {
1073 executeCreateChild(newParent, copyName);
1076 private static void executeMove(final VirtualFile file, final VirtualFile newParent) {
1077 final int fileId = getFileId(file);
1078 final int newParentId = getFileId(newParent);
1079 final int oldParentId = getFileId(file.getParent());
1081 removeIdFromParentList(oldParentId, fileId, file.getParent(), file);
1082 appendIdToParentList(newParentId, fileId);
1084 ((VirtualFileSystemEntry)file).setParent(newParent);
1085 FSRecords.setParent(fileId, newParentId);
1088 public String getName(final int id) {
1090 return FSRecords.getName(id);
1093 public void cleanPersistedContents() {
1095 final int[] roots = FSRecords.listRoots();
1096 for (int root : roots) {
1097 cleanPersistedContentsRecursively(root);
1100 catch (IOException e) {
1101 throw new RuntimeException(e);
1105 private static void cleanPersistedContentsRecursively(int id) {
1106 if (isDirectory(id)) {
1107 for (int child : FSRecords.list(id)) {
1108 cleanPersistedContentsRecursively(child);
1112 setFlag(id, MUST_RELOAD_CONTENT, true);
1116 public static NewVirtualFileSystem replaceWithNativeFS(NewVirtualFileSystem delegate) {
1117 if (delegate.getProtocol().equals(LocalFileSystem.PROTOCOL) && Registry.is("filesystem.useNative")) {
1118 if (SystemInfo.isWindows && Win32LocalFileSystem.isAvailable()) {
1119 delegate = Win32LocalFileSystem.getWin32Instance();