2 * Copyright 2000-2014 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.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;
42 import org.jetbrains.annotations.NonNls;
43 import org.jetbrains.annotations.NotNull;
44 import org.jetbrains.annotations.Nullable;
45 import org.jetbrains.annotations.TestOnly;
49 import java.util.concurrent.atomic.AtomicBoolean;
50 import java.util.concurrent.locks.ReadWriteLock;
51 import java.util.concurrent.locks.ReentrantReadWriteLock;
56 public class PersistentFSImpl extends PersistentFS implements ApplicationComponent {
57 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.newvfs.persistent.PersistentFS");
59 private final MessageBus myEventBus;
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>();
65 private final ConcurrentIntObjectMap<VirtualFileSystemEntry> myIdToDirCache = ContainerUtil.createConcurrentIntObjectMap();
66 private final Object myInputLock = new Object();
68 private final AtomicBoolean myShutDown = new AtomicBoolean(false);
69 @SuppressWarnings("FieldCanBeLocal")
70 private final LowMemoryWatcher myWatcher = LowMemoryWatcher.register(new Runnable() {
77 public PersistentFSImpl(@NotNull MessageBus bus) {
79 ShutDownTracker.getInstance().registerShutdownTask(new Runnable() {
88 public void initComponent() {
93 public void disposeComponent() {
97 private void performShutdown() {
98 if (myShutDown.compareAndSet(false, true)) {
99 LOG.info("VFS dispose started");
101 LOG.info("VFS dispose completed");
108 public String getComponentName() {
109 return "app.component.PersistentFS";
113 public boolean areChildrenLoaded(@NotNull final VirtualFile dir) {
114 return areChildrenLoaded(getFileId(dir));
118 public long getCreationTimestamp() {
119 return FSRecords.getCreationTimestamp();
123 private static NewVirtualFileSystem getDelegate(@NotNull VirtualFile file) {
124 return (NewVirtualFileSystem)file.getFileSystem();
128 public boolean wereChildrenAccessed(@NotNull final VirtualFile dir) {
129 return FSRecords.wereChildrenAccessed(getFileId(dir));
134 public String[] list(@NotNull final VirtualFile file) {
135 int id = getFileId(file);
137 FSRecords.NameId[] nameIds = FSRecords.listAll(id);
138 if (!areChildrenLoaded(id)) {
139 nameIds = persistAllChildren(file, id, nameIds);
141 return ContainerUtil.map2Array(nameIds, String.class, new Function<FSRecords.NameId, String>() {
143 public String fun(FSRecords.NameId id) {
144 return id.name.toString();
151 public String[] listPersisted(@NotNull VirtualFile parent) {
152 return listPersisted(FSRecords.list(getFileId(parent)));
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]);
165 private static FSRecords.NameId[] persistAllChildren(@NotNull final VirtualFile file, final int id, @NotNull FSRecords.NameId[] current) {
166 final NewVirtualFileSystem fs = replaceWithNativeFS(getDelegate(file));
168 String[] delegateNames = VfsUtil.filterNames(fs.list(file));
169 if (delegateNames.length == 0 && current.length > 0) {
173 Set<String> toAdd = ContainerUtil.newHashSet(delegateNames);
174 for (FSRecords.NameId nameId : current) {
175 toAdd.remove(nameId.name.toString());
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);
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));
194 FSRecords.updateList(id, childrenIds.toNativeArray());
195 setChildrenCached(id);
197 return nameIds.toArray(new FSRecords.NameId[nameIds.size()]);
200 public static void setChildrenCached(int id) {
201 int flags = FSRecords.getFlags(id);
202 FSRecords.setFlags(id, flags | CHILDREN_CACHED_FLAG, true);
207 public FSRecords.NameId[] listAll(@NotNull VirtualFile parent) {
208 final int parentId = getFileId(parent);
210 FSRecords.NameId[] nameIds = FSRecords.listAll(parentId);
211 if (!areChildrenLoaded(parentId)) {
212 return persistAllChildren(parent, parentId, nameIds);
218 private static boolean areChildrenLoaded(final int parentId) {
219 return (FSRecords.getFlags(parentId) & CHILDREN_CACHED_FLAG) != 0;
224 public DataInputStream readAttribute(@NotNull final VirtualFile file, @NotNull final FileAttribute att) {
225 return FSRecords.readAttributeWithLock(getFileId(file), att);
230 public DataOutputStream writeAttribute(@NotNull final VirtualFile file, @NotNull final FileAttribute att) {
231 return FSRecords.writeAttribute(getFileId(file), att);
235 private static DataInputStream readContent(@NotNull VirtualFile file) {
236 return FSRecords.readContent(getFileId(file));
240 private static DataInputStream readContentById(int contentId) {
241 return FSRecords.readContentById(contentId);
245 private static DataOutputStream writeContent(@NotNull VirtualFile file, boolean readOnly) {
246 return FSRecords.writeContent(getFileId(file), readOnly);
249 private static void writeContent(@NotNull VirtualFile file, ByteSequence content, boolean readOnly) throws IOException {
250 FSRecords.writeContent(getFileId(file), content, readOnly);
254 public int storeUnlinkedContent(@NotNull byte[] bytes) {
255 return FSRecords.storeUnlinkedContent(bytes);
259 public int getModificationCount(@NotNull final VirtualFile file) {
260 return FSRecords.getModCount(getFileId(file));
264 public int getCheapFileSystemModificationCount() {
265 return FSRecords.getLocalModCount();
269 public int getFilesystemModificationCount() {
270 return FSRecords.getModCount();
273 private static boolean writeAttributesToRecord(final int id,
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.
283 if (areChildrenLoaded(id)) return false; // TODO: hack
286 FSRecords.writeAttributesToRecord(id, parentId, attributes, name);
292 public int getFileAttributes(int id) {
294 //noinspection MagicConstant
295 return FSRecords.getFlags(id);
299 public boolean isDirectory(@NotNull final VirtualFile file) {
300 return isDirectory(getFileAttributes(getFileId(file)));
303 private static int getParent(final int id) {
305 return FSRecords.getParent(id);
308 private static boolean namesEqual(@NotNull VirtualFileSystem fs, @NotNull String n1, String n2) {
309 return fs.isCaseSensitive() ? n1.equals(n2) : n1.equalsIgnoreCase(n2);
313 public boolean exists(@NotNull final VirtualFile fileOrDirectory) {
314 return ((VirtualFileWithId)fileOrDirectory).getId() > 0;
318 public long getTimeStamp(@NotNull final VirtualFile file) {
319 return FSRecords.getTimestamp(getFileId(file));
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);
329 private static int getFileId(@NotNull VirtualFile file) {
330 final int id = ((VirtualFileWithId)file).getId();
332 throw new InvalidVirtualFileAccessException(file);
338 public boolean isSymLink(@NotNull VirtualFile file) {
339 return isSymLink(getFileAttributes(getFileId(file)));
343 public String resolveSymLink(@NotNull VirtualFile file) {
344 throw new UnsupportedOperationException();
348 public boolean isSpecialFile(@NotNull VirtualFile file) {
349 return isSpecialFile(getFileAttributes(getFileId(file)));
353 public boolean isWritable(@NotNull VirtualFile file) {
354 return (getFileAttributes(getFileId(file)) & IS_READ_ONLY) == 0;
358 public boolean isHidden(@NotNull VirtualFile file) {
359 return (getFileAttributes(getFileId(file)) & IS_HIDDEN) != 0;
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));
372 public int getId(@NotNull VirtualFile parent, @NotNull String childName, @NotNull NewVirtualFileSystem fs) {
373 int parentId = getFileId(parent);
374 int[] children = FSRecords.list(parentId);
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)) {
384 // for case sensitive system the above check is exhaustive in consistent state of vfs
387 for (final int childId : children) {
388 if (namesEqual(fs, childName, FSRecords.getName(childId))) return childId;
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));
403 public long getLength(@NotNull final VirtualFile file) {
405 if (mustReloadContent(file)) {
406 len = reloadLengthFromDelegate(file, getDelegate(file));
409 final int id = getFileId(file);
410 len = FSRecords.getLength(id);
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));
422 final VirtualFile child = parent.findChild(name);
424 throw new IOException("Cannot create child");
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));
435 final VirtualFile child = parent.findChild(dir);
437 throw new IOException("Cannot create child directory '" + dir + "' at " + parent.getPath());
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));
448 final VirtualFile child = parent.findChild(file);
450 throw new IOException("Cannot create child file '" + file + "' at " + parent.getPath());
456 public void deleteFile(final Object requestor, @NotNull final VirtualFile file) throws IOException {
457 final NewVirtualFileSystem delegate = getDelegate(file);
458 delegate.deleteFile(requestor, file);
460 if (!delegate.exists(file)) {
461 processEvent(new VFileDeleteEvent(requestor, file, false));
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));
476 public byte[] contentsToByteArray(@NotNull final VirtualFile file) throws IOException {
477 return contentsToByteArray(file, true);
482 public byte[] contentsToByteArray(@NotNull final VirtualFile file, boolean cacheContent) throws IOException {
483 InputStream contentStream = null;
484 boolean reloadFromDelegate;
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;
493 if (reloadFromDelegate) {
494 final NewVirtualFileSystem delegate = getDelegate(file);
496 final byte[] content;
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);
504 // a bit of optimization
505 content = delegate.contentsToByteArray(file);
506 FSRecords.setLength(fileId, content.length);
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);
527 final int length = (int)file.getLength();
528 assert length >= 0 : file;
529 return FileUtil.loadBytes(contentStream, length);
531 catch (IOException e) {
532 throw FSRecords.handleError(e);
539 public byte[] contentsToByteArray(int contentId) throws IOException {
540 final DataInputStream stream = readContentById(contentId);
541 assert stream != null : contentId;
542 return FileUtil.loadBytes(stream);
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);
555 if (len > PersistentFSConstants.FILE_LENGTH_TO_CACHE_THRESHOLD) return nativeStream;
556 return createReplicator(file, nativeStream, len, delegate.isReadOnly());
559 return contentStream;
564 private static long reloadLengthFromDelegate(@NotNull VirtualFile file, @NotNull NewVirtualFileSystem delegate) {
565 final long len = delegate.getLength(file);
566 FSRecords.setLength(getFileId(file), len);
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) {
576 BufferExposingByteArrayInputStream byteStream = (BufferExposingByteArrayInputStream )nativeStream;
577 byte[] bytes = byteStream.getInternalBuffer();
578 storeContentToStorage(fileLength, file, readOnly, bytes, bytes.length);
581 @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
582 final BufferExposingByteArrayOutputStream cache = new BufferExposingByteArrayOutputStream((int)fileLength);
583 return new ReplicatorInputStream(nativeStream, cache) {
585 public void close() throws IOException {
587 storeContentToStorage(fileLength, file, readOnly, cache.getInternalBuffer(), cache.size());
592 private void storeContentToStorage(long fileLength,
593 @NotNull VirtualFile file,
594 boolean readOnly, @NotNull byte[] bytes, int bytesLength)
596 synchronized (myInputLock) {
597 if (bytesLength == fileLength) {
598 writeContent(file, new ByteSequence(bytes, 0, bytesLength), readOnly);
599 setFlag(file, MUST_RELOAD_CONTENT, false);
602 setFlag(file, MUST_RELOAD_CONTENT, true);
607 private static boolean mustReloadContent(@NotNull VirtualFile file) {
608 int fileId = getFileId(file);
609 return checkFlag(fileId, MUST_RELOAD_CONTENT) || FSRecords.getLength(fileId) == -1L;
614 public OutputStream getOutputStream(@NotNull final VirtualFile file,
615 final Object requestor,
617 final long timeStamp) throws IOException {
618 return new ByteArrayOutputStream() {
619 private boolean closed; // protection against user calling .close() twice
622 public void close() throws IOException {
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);
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());
637 persistenceStream.write(buf, 0, count);
641 ioFileStream.write(buf, 0, count);
645 persistenceStream.close();
646 ioFileStream.close();
648 executeTouch(file, false, event.getModificationStamp());
649 publisher.after(events);
657 public int acquireContent(@NotNull VirtualFile file) {
658 return FSRecords.acquireFileContent(getFileId(file));
662 public void releaseContent(int contentId) {
663 FSRecords.releaseContent(contentId);
667 public int getCurrentContentId(@NotNull VirtualFile file) {
668 return FSRecords.getContentId(getFileId(file));
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));
677 private void processEvent(@NotNull VFileEvent event) {
678 processEvents(Collections.singletonList(event));
681 private static class EventWrapper {
682 private final VFileDeleteEvent event;
683 private final int id;
685 private EventWrapper(final VFileDeleteEvent event, final int id) {
691 @NotNull private static final Comparator<EventWrapper> DEPTH_COMPARATOR = new Comparator<EventWrapper>() {
693 public int compare(@NotNull final EventWrapper o1, @NotNull final EventWrapper o2) {
694 return o1.event.getFileDepth() - o2.event.getFileDepth();
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));
708 final TIntHashSet invalidIDs;
709 if (deletionEvents.isEmpty()) {
710 invalidIDs = EmptyIntHashSet.INSTANCE;
713 ContainerUtil.quickSort(deletionEvents, DEPTH_COMPARATOR);
715 invalidIDs = new TIntHashSet(deletionEvents.size());
716 final Set<VirtualFile> dirsToBeDeleted = new THashSet<VirtualFile>(deletionEvents.size());
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);
726 parent = parent.getParent();
729 if (candidate.isDirectory()) {
730 dirsToBeDeleted.add(candidate);
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))) {
746 public void processEvents(@NotNull List<VFileEvent> events) {
747 ApplicationManager.getApplication().assertWriteAccessAllowed();
749 List<VFileEvent> validated = validateEvents(events);
751 BulkFileListener publisher = myEventBus.syncPublisher(VirtualFileManager.VFS_CHANGES);
752 publisher.before(validated);
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();
761 else if (event instanceof VFileDeleteEvent) {
762 changedParent = ((VFileDeleteEvent)event).getFile().getParent();
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>());
771 parentChildrenChanges.add(event);
778 if (parentToChildrenEventsChanges != null) {
779 parentToChildrenEventsChanges.forEachEntry(new TObjectObjectProcedure<VirtualFile, List<VFileEvent>>() {
781 public boolean execute(VirtualFile parent, List<VFileEvent> childrenEvents) {
782 applyChildrenChangeEvents(parent, childrenEvents);
786 parentToChildrenEventsChanges.clear();
789 publisher.after(validated);
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>();
797 final int parentId = getFileId(parent);
798 assert parentId != 0;
799 TIntHashSet parentChildrenIds = new TIntHashSet(FSRecords.list(parentId));
800 boolean hasRemovedChildren = false;
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);
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);
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());
825 hasRemovedChildren = true;
826 int id = getFileId(file);
828 childrenToBeUpdated.add(file);
829 childrenIdsUpdated.add(-id);
830 parentChildrenIds.remove(id);
834 FSRecords.updateList(parentId, parentChildrenIds.toArray());
836 if (hasRemovedChildren) clearIdCache();
837 VirtualDirectoryImpl parentImpl = (VirtualDirectoryImpl)parent;
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);
844 parentImpl.addChild((VirtualFileSystemEntry)childFile);
847 FSRecords.deleteRecordRecursively(-childId);
848 parentImpl.removeChild(childFile);
849 invalidateSubtree(childFile);
856 public VirtualFileSystemEntry findRoot(@NotNull String basePath, @NotNull NewVirtualFileSystem fs) {
857 if (basePath.isEmpty()) {
858 LOG.error("Invalid root, fs=" + fs);
862 String rootUrl = normalizeRootUrl(basePath, fs);
864 myRootsLock.readLock().lock();
866 VirtualFileSystemEntry root = myRoots.get(rootUrl);
867 if (root != null) return root;
870 myRootsLock.readLock().unlock();
873 final VirtualFileSystemEntry newRoot;
874 int rootId = FSRecords.findRootRecord(rootUrl);
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);
887 newRoot = new FsRoot(fs, rootId, segment, directoryData, basePath);
890 FileAttributes attributes = fs.getAttributes(new StubVirtualFile() {
893 public String getPath() {
894 return newRoot.getPath();
899 public VirtualFile getParent() {
903 if (attributes == null || !attributes.isDirectory()) {
907 boolean mark = false;
909 myRootsLock.writeLock().lock();
911 VirtualFileSystemEntry root = myRoots.get(rootUrl);
912 if (root != null) return root;
914 VfsData.initFile(rootId, segment, -1, directoryData);
915 mark = writeAttributesToRecord(rootId, 0, newRoot, fs, attributes);
917 myRoots.put(rootUrl, newRoot);
918 myRootsById.put(rootId, newRoot);
921 myRootsLock.writeLock().unlock();
924 if (!mark && attributes.lastModified != FSRecords.getTimestamp(rootId)) {
925 newRoot.markDirtyRecursively();
928 LOG.assertTrue(rootId == newRoot.getId(), "root=" + newRoot + " expected=" + rootId + " actual=" + newRoot.getId());
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)));
941 public void clearIdCache() {
942 myIdToDirCache.clear();
945 private static final int DEPTH_LIMIT = 75;
949 public NewVirtualFile findFileById(final int id) {
950 return findFileById(id, false, null, 0);
954 public NewVirtualFile findFileByIdIfCached(final int id) {
955 return findFileById(id, true, null, 0);
959 private VirtualFileSystemEntry findFileById(int id, boolean cachedOnly, TIntArrayList visited, int mask) {
960 VirtualFileSystemEntry cached = myIdToDirCache.get(id);
961 if (cached != null) return cached;
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);
974 int parentId = getParent(id);
975 if (parentId >= id) {
976 if (visited == null) visited = new TIntArrayList(DEPTH_LIMIT);
978 if (visited != null) visited.add(id);
980 VirtualFileSystemEntry result;
982 myRootsLock.readLock().lock();
984 result = myRootsById.get(id);
987 myRootsLock.readLock().unlock();
991 VirtualFileSystemEntry parentFile = findFileById(parentId, cachedOnly, visited, mask | id);
992 if (parentFile instanceof VirtualDirectoryImpl) {
993 result = ((VirtualDirectoryImpl)parentFile).findChildById(id, cachedOnly);
1000 if (result != null && result.isDirectory()) {
1001 VirtualFileSystemEntry old = myIdToDirCache.put(id, result);
1002 if (old != null) result = old;
1009 public VirtualFile[] getRoots() {
1010 myRootsLock.readLock().lock();
1012 Collection<VirtualFileSystemEntry> roots = myRoots.values();
1013 return VfsUtilCore.toVirtualFileArray(roots);
1016 myRootsLock.readLock().unlock();
1022 public VirtualFile[] getRoots(@NotNull final NewVirtualFileSystem fs) {
1023 final List<VirtualFile> roots = new ArrayList<VirtualFile>();
1025 myRootsLock.readLock().lock();
1027 for (NewVirtualFile root : myRoots.values()) {
1028 if (root.getFileSystem() == fs) {
1034 myRootsLock.readLock().unlock();
1037 return VfsUtilCore.toVirtualFileArray(roots);
1042 public VirtualFile[] getLocalRoots() {
1043 List<VirtualFile> roots = ContainerUtil.newSmartList();
1045 myRootsLock.readLock().lock();
1047 for (NewVirtualFile root : myRoots.values()) {
1048 if (root.isInLocalFileSystem() && !(root.getFileSystem() instanceof TempFileSystem)) {
1054 myRootsLock.readLock().unlock();
1057 return VfsUtilCore.toVirtualFileArray(roots);
1060 private VirtualFileSystemEntry applyEvent(@NotNull VFileEvent event) {
1062 if (event instanceof VFileCreateEvent) {
1063 final VFileCreateEvent createEvent = (VFileCreateEvent)event;
1064 return executeCreateChild(createEvent.getParent(), createEvent.getChildName());
1066 else if (event instanceof VFileDeleteEvent) {
1067 final VFileDeleteEvent deleteEvent = (VFileDeleteEvent)event;
1068 executeDelete(deleteEvent.getFile());
1070 else if (event instanceof VFileContentChangeEvent) {
1071 final VFileContentChangeEvent contentUpdateEvent = (VFileContentChangeEvent)event;
1072 executeTouch(contentUpdateEvent.getFile(), contentUpdateEvent.isFromRefresh(), contentUpdateEvent.getModificationStamp());
1074 else if (event instanceof VFileCopyEvent) {
1075 final VFileCopyEvent copyEvent = (VFileCopyEvent)event;
1076 return executeCreateChild(copyEvent.getNewParent(), copyEvent.getNewChildName());
1078 else if (event instanceof VFileMoveEvent) {
1079 final VFileMoveEvent moveEvent = (VFileMoveEvent)event;
1080 executeMove(moveEvent.getFile(), moveEvent.getNewParent());
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());
1087 else if (VirtualFile.PROP_WRITABLE.equals(propertyChangeEvent.getPropertyName())) {
1088 executeSetWritable(propertyChangeEvent.getFile(), ((Boolean)propertyChangeEvent.getNewValue()).booleanValue());
1090 else if (VirtualFile.PROP_HIDDEN.equals(propertyChangeEvent.getPropertyName())) {
1091 executeSetHidden(propertyChangeEvent.getFile(), ((Boolean)propertyChangeEvent.getNewValue()).booleanValue());
1093 else if (VirtualFile.PROP_SYMLINK_TARGET.equals(propertyChangeEvent.getPropertyName())) {
1094 executeSetTarget(propertyChangeEvent.getFile(), (String)propertyChangeEvent.getNewValue());
1098 catch (Exception e) {
1099 // Exception applying single event should not prevent other events from applying.
1107 public String toString() {
1108 return "PersistentFS";
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);
1128 private static int createAndFillRecord(@NotNull NewVirtualFileSystem delegateSystem,
1129 @NotNull VirtualFile delegateFile,
1131 @NotNull FileAttributes attributes) {
1132 final int childId = FSRecords.createRecord();
1133 writeAttributesToRecord(childId, parentId, delegateFile, delegateSystem, attributes);
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);
1143 private void executeDelete(@NotNull VirtualFile file) {
1144 if (!file.exists()) {
1145 LOG.error("Deleting a file, which does not exist: " + file.getPath());
1150 int id = getFileId(file);
1152 final VirtualFile parent = file.getParent();
1153 final int parentId = parent == null ? 0 : getFileId(parent);
1155 if (parentId == 0) {
1156 String rootUrl = normalizeRootUrl(file.getPath(), (NewVirtualFileSystem)file.getFileSystem());
1157 myRootsLock.writeLock().lock();
1159 myRoots.remove(rootUrl);
1160 myRootsById.remove(id);
1161 FSRecords.deleteRootRecord(id);
1164 myRootsLock.writeLock().unlock();
1168 removeIdFromParentList(parentId, id, parent, file);
1169 VirtualDirectoryImpl directory = (VirtualDirectoryImpl)file.getParent();
1170 assert directory != null : file;
1171 directory.removeChild(file);
1174 FSRecords.deleteRecordRecursively(id);
1176 invalidateSubtree(file);
1179 private static void invalidateSubtree(@NotNull VirtualFile file) {
1180 final VirtualFileSystemEntry impl = (VirtualFileSystemEntry)file;
1182 for (VirtualFile child : impl.getCachedChildren()) {
1183 invalidateSubtree(child);
1187 private static void removeIdFromParentList(final int parentId, final int id, @NotNull VirtualFile parent, VirtualFile file) {
1188 int[] childList = FSRecords.list(parentId);
1190 int index = ArrayUtil.indexOf(childList, id);
1192 throw new RuntimeException("Cannot find child (" + id + ")" + file
1193 + "\n\tin (" + parentId + ")" + parent
1194 + "\n\tactual children:" + Arrays.toString(childList));
1196 childList = ArrayUtil.remove(childList, index);
1197 FSRecords.updateList(parentId, childList);
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);
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);
1211 private static void executeSetHidden(@NotNull VirtualFile file, boolean hiddenFlag) {
1212 setFlag(file, IS_HIDDEN, hiddenFlag);
1213 ((VirtualFileSystemEntry)file).updateProperty(VirtualFile.PROP_HIDDEN, hiddenFlag);
1216 private static void executeSetTarget(@NotNull VirtualFile file, String target) {
1217 ((VirtualFileSystemEntry)file).setLinkTarget(target);
1220 private static void setFlag(@NotNull VirtualFile file, int mask, boolean value) {
1221 setFlag(getFileId(file), mask, value);
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;
1228 if (oldFlags != flags) {
1229 FSRecords.setFlags(id, flags, true);
1233 private static boolean checkFlag(int fileId, int mask) {
1234 return (FSRecords.getFlags(fileId) & mask) != 0;
1237 private static void executeTouch(@NotNull VirtualFile file, boolean reloadContentFromDelegate, long newModificationStamp) {
1238 if (reloadContentFromDelegate) {
1239 setFlag(file, MUST_RELOAD_CONTENT, true);
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);
1247 ((VirtualFileSystemEntry)file).setModificationStamp(newModificationStamp);
1250 private void executeMove(@NotNull VirtualFile file, @NotNull VirtualFile newParent) {
1253 final int fileId = getFileId(file);
1254 final int newParentId = getFileId(newParent);
1255 final int oldParentId = getFileId(file.getParent());
1257 removeIdFromParentList(oldParentId, fileId, file.getParent(), file);
1258 FSRecords.setParent(fileId, newParentId);
1259 appendIdToParentList(newParentId, fileId);
1260 ((VirtualFileSystemEntry)file).setParent(newParent);
1264 public String getName(int id) {
1266 return FSRecords.getName(id);
1270 public void cleanPersistedContents() {
1271 final int[] roots = FSRecords.listRoots();
1272 for (int root : roots) {
1273 cleanPersistedContentsRecursively(root);
1278 private void cleanPersistedContentsRecursively(int id) {
1279 if (isDirectory(getFileAttributes(id))) {
1280 for (int child : FSRecords.list(id)) {
1281 cleanPersistedContentsRecursively(child);
1285 setFlag(id, MUST_RELOAD_CONTENT, true);
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);
1297 public abstract CharSequence getNameSequence();
1300 protected abstract char[] appendPathOnFileSystem(int accumulatedPathLength, int[] positionRef);
1303 public void setNewName(@NotNull String newName) {
1304 throw new IncorrectOperationException();
1308 public final void setParent(@NotNull VirtualFile newParent) {
1309 throw new IncorrectOperationException();
1313 private static class JarRoot extends AbstractRoot {
1314 private final VirtualFile myParentLocalFile;
1315 private final String myParentPath;
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();
1325 public CharSequence getNameSequence() {
1326 return myParentLocalFile.getNameSequence();
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);
1338 private static class FsRoot extends AbstractRoot {
1339 private final String myName;
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);
1348 public CharSequence getNameSequence() {
1353 protected char[] appendPathOnFileSystem(int pathLength, int[] position) {
1354 String name = getName();
1355 int nameLength = name.length();
1356 int rootPathLength = pathLength + nameLength;
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;
1361 if (appendSlash) ++rootPathLength;
1362 char[] chars = new char[rootPathLength];
1364 position[0] = copyString(chars, position[0], name);
1367 chars[position[0]++] = '/';