diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/VfsImplUtil.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/VfsImplUtil.java
index a4a98d667c94..e2730da5cbf1 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/VfsImplUtil.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/VfsImplUtil.java
@@ -167,7 +167,7 @@ public class VfsImplUtil {
*
* FileDocumentManager.getInstance().saveDocument(document);
* runExternalToolToChangeFile(virtualFile.getPath()) // changes file externally in milliseconds, probably without changing file's length
- * VfsUtil.markDirtyAndRefresh(true, true, true, virtualFile); // might be replace with {@link #forceSyncRefresh(VirtualFile)}
+ * VfsUtil.markDirtyAndRefresh(true, true, true, virtualFile); // might be replaced with {@link #forceSyncRefresh(VirtualFile)}
*
*/
public static void forceSyncRefresh(@NotNull VirtualFile file) {
@@ -236,20 +236,9 @@ public class VfsImplUtil {
for (VFileEvent event : events) {
if (!(event.getFileSystem() instanceof LocalFileSystem)) continue;
- if (event instanceof VFileCreateEvent) continue; // new files don't affect existing handlers (and getFile() is costly)
-
- if (event instanceof VFilePropertyChangeEvent &&
- !VirtualFile.PROP_NAME.equals(((VFilePropertyChangeEvent)event).getPropertyName())) {
- continue;
- }
+ if (!(event instanceof VFileContentChangeEvent)) continue;
String path = event.getPath();
- if (event instanceof VFilePropertyChangeEvent) {
- path = ((VFilePropertyChangeEvent)event).getOldPath();
- }
- else if (event instanceof VFileMoveEvent) {
- path = ((VFileMoveEvent)event).getOldPath();
- }
VirtualFile file = event.getFile();
if (file == null || !file.isDirectory()) {
@@ -330,4 +319,63 @@ public class VfsImplUtil {
if (state == null) throw new IllegalArgumentException(localPath + " not in " + ourHandlerCache.keySet());
}
}
+
+ /**
+ * check whether {@code event} (in LocalFileSystem) affects some jars and if so, generate appropriate additional JarFileSystem-events and corresponding after-event-actions.
+ * For example, "delete/change/move '/tmp/x.jar'" event should generate "delete jar:///tmp/x.jar!/" events.
+ */
+ @NotNull
+ public static List getJarInvalidationEvents(@NotNull VFileEvent event, @NotNull List super Runnable> outApplyActions) {
+ if (!(event instanceof VFileDeleteEvent ||
+ event instanceof VFileMoveEvent ||
+ event instanceof VFilePropertyChangeEvent && VirtualFile.PROP_NAME.equals(((VFilePropertyChangeEvent)event).getPropertyName()))) {
+ return Collections.emptyList();
+ }
+ String path;
+ if (event instanceof VFilePropertyChangeEvent) {
+ path = ((VFilePropertyChangeEvent)event).getOldPath();
+ }
+ else if (event instanceof VFileMoveEvent) {
+ path = ((VFileMoveEvent)event).getOldPath();
+ }
+ else {
+ path = event.getPath();
+ }
+
+ VirtualFile file = event.getFile();
+ if (file == null) {
+ return Collections.emptyList();
+ }
+ Collection jarPaths = ourDominatorsMap.get(path);
+ if (jarPaths == null) {
+ jarPaths = Collections.singletonList(path);
+ }
+ List events = new ArrayList<>(jarPaths.size());
+ for (String jarPath : jarPaths) {
+ Pair handlerPair = ourHandlerCache.get(jarPath);
+ if (handlerPair == null) {
+ continue;
+ }
+ ArchiveFileSystem fileSystem = handlerPair.first;
+ NewVirtualFile root = ManagingFS.getInstance().findRoot(fileSystem.composeRootPath(jarPath), fileSystem);
+ if (root != null) {
+ VFileDeleteEvent jarDeleteEvent = new VFileDeleteEvent(event.getRequestor(), root, event.isFromRefresh());
+ Runnable runnable = () -> {
+ Pair pair = ourHandlerCache.remove(jarPath);
+ if (pair != null) {
+ pair.second.dispose();
+ forEachDirectoryComponent(jarPath, containingDirectoryPath -> {
+ Set handlers = ourDominatorsMap.get(containingDirectoryPath);
+ if (handlers != null && handlers.remove(jarPath) && handlers.isEmpty()) {
+ ourDominatorsMap.remove(containingDirectoryPath);
+ }
+ });
+ }
+ };
+ events.add(jarDeleteEvent);
+ outApplyActions.add(runnable);
+ }
+ }
+ return events;
+ }
}
\ No newline at end of file
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java
index f3aefcd48420..c72f552ae87e 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java
@@ -722,14 +722,31 @@ public final class PersistentFSImpl extends PersistentFS implements Disposable {
private void processEvent(@NotNull VFileEvent event) {
ApplicationManager.getApplication().assertWriteAccessAllowed();
- // optimisation: skip all groupings
- if (event.isValid()) {
- List events = Collections.singletonList(event);
- getPublisher().before(events);
+ if (!event.isValid()) {
+ return;
+ }
+ List outValidatedEvents = new ArrayList<>();
+ outValidatedEvents.add(event);
+ List outApplyActions = new ArrayList<>();
+ List jarDeleteEvents = VfsImplUtil.getJarInvalidationEvents(event, outApplyActions);
+ BulkFileListener publisher = getPublisher();
+ if (jarDeleteEvents.isEmpty() && outApplyActions.isEmpty()) {
+ // optimisation: skip all groupings
+ publisher.before(outValidatedEvents);
applyEvent(event);
- getPublisher().after(events);
+ publisher.after(outValidatedEvents);
+ }
+ else {
+ outApplyActions.add(() -> applyEvent(event));
+ // there are a number of additional jar events generated
+ for (VFileDeleteEvent jarDeleteEvent : jarDeleteEvents) {
+ outApplyActions.add(() -> applyEvent(jarDeleteEvent));
+ outValidatedEvents.add(jarDeleteEvent);
+ }
+
+ applyMultipleEvents(publisher, outApplyActions, outValidatedEvents);
}
}
@@ -874,6 +891,15 @@ public final class PersistentFSImpl extends PersistentFS implements Disposable {
groupDeletions(events, startIndex, endIndex, outValidatedEvents, outApplyActions, toIgnore);
groupOthers(events, startIndex, endIndex, outValidatedEvents, outApplyActions);
+ for (int i = startIndex; i < endIndex; i++) {
+ VFileEvent event = events.get(i);
+ List jarDeleteEvents = VfsImplUtil.getJarInvalidationEvents(event, outApplyActions);
+ for (VFileDeleteEvent jarDeleteEvent : jarDeleteEvents) {
+ outApplyActions.add((Runnable)() -> applyEvent(jarDeleteEvent));
+ outValidatedEvents.add(jarDeleteEvent);
+ }
+ }
+
return endIndex;
}
@@ -989,20 +1015,26 @@ public final class PersistentFSImpl extends PersistentFS implements Disposable {
startIndex = groupAndValidate(events, startIndex, applyActions, validated, files, middleDirs);
if (!validated.isEmpty()) {
- PingProgress.interactWithEdtProgress();
- // do defensive copy to cope with ill-written listeners that save passed list for later processing
- List toSend = ContainerUtil.immutableList(validated.toArray(new VFileEvent[0]));
- publisher.before(toSend);
-
- PingProgress.interactWithEdtProgress();
- applyActions.forEach(Runnable::run);
-
- PingProgress.interactWithEdtProgress();
- publisher.after(toSend);
+ applyMultipleEvents(publisher, applyActions, validated);
}
}
}
+ private static void applyMultipleEvents(@NotNull BulkFileListener publisher,
+ @NotNull List extends Runnable> applyActions,
+ @NotNull List extends VFileEvent> applyEvents) {
+ PingProgress.interactWithEdtProgress();
+ // do defensive copy to cope with ill-written listeners that save passed list for later processing
+ List toSend = ContainerUtil.immutableList(applyEvents.toArray(new VFileEvent[0]));
+ publisher.before(toSend);
+
+ PingProgress.interactWithEdtProgress();
+ applyActions.forEach(Runnable::run);
+
+ PingProgress.interactWithEdtProgress();
+ publisher.after(toSend);
+ }
+
// remove children from specified directories using VirtualDirectoryImpl.removeChildren() optimised for bulk removals
private void applyDeletions(@NotNull MultiMap deletions) {
for (Map.Entry> entry : deletions.entrySet()) {
diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/JarFileSystemTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/JarFileSystemTest.java
index 097b61451062..fcec3456e5f4 100644
--- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/JarFileSystemTest.java
+++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/JarFileSystemTest.java
@@ -18,9 +18,11 @@ import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent;
import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
import com.intellij.testFramework.PlatformTestUtil;
import com.intellij.testFramework.UsefulTestCase;
+import com.intellij.testFramework.VfsTestUtil;
import com.intellij.testFramework.fixtures.BareTestFixtureTestCase;
import com.intellij.testFramework.rules.TempDirectory;
import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.messages.MessageBusConnection;
import org.jetbrains.annotations.NotNull;
import org.junit.After;
import org.junit.Rule;
@@ -39,8 +41,6 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.jar.JarFile;
import java.util.stream.Stream;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
import static com.intellij.openapi.util.io.IoTestUtil.assertTimestampsEqual;
import static com.intellij.testFramework.PlatformTestUtil.assertPathsEqual;
@@ -225,22 +225,20 @@ public class JarFileSystemTest extends BareTestFixtureTestCase {
}
@Test
- public void testInvalidZip() throws IOException {
- File testZip = tempDir.newFile("test.zip");
- try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(testZip))) {
- writeEntry(zip, "a");
- writeEntry(zip, "a/b");
- writeEntry(zip, "a/b/c.txt");
- writeEntry(zip, "x\\y\\z.txt");
- writeEntry(zip, "/x/f.txt");
- writeEntry(zip, "d1/aB");
- writeEntry(zip, "d1/ab");
- writeEntry(zip, "D2/f1");
- writeEntry(zip, "d2/f2");
- }
+ public void testInvalidZip() {
+ VirtualFile vf = createJar(
+ "a"
+ , "a/b"
+ , "a/b/c.txt"
+ , "x\\y\\z.txt"
+ , "/x/f.txt"
+ , "d1/aB"
+ , "d1/ab"
+ , "D2/f1"
+ , "d2/f2");
- String rootPath = FileUtil.toSystemIndependentName(testZip.getPath()) + JarFileSystem.JAR_SEPARATOR;
- VirtualFile jarRoot = JarFileSystem.getInstance().findFileByPath(rootPath);
+ String rootPath = vf.getPath() + JarFileSystem.JAR_SEPARATOR;
+ VirtualFile jarRoot = JarFileSystem.getInstance().getJarRootForLocalFile(vf);
assertNotNull(jarRoot);
List entries = new ArrayList<>();
VfsUtilCore.visitChildrenRecursively(jarRoot, new VirtualFileVisitor