@Override
  public void after(@NotNull final List<? extends VFileEvent> events) {
    incModificationCount();

    for (FilePointerPartNode node : myPointersToUpdateUrl) {
      synchronized (this) {
        VirtualFilePointerImpl pointer = node.leaf;
        String urlBefore = pointer.getUrlNoUpdate();
        Pair<VirtualFile, String> after = node.update();
        String urlAfter = after.second;
        if (URL_COMPARATOR.compare(urlBefore, urlAfter) != 0) {
          // url has changed, reinsert
          FilePointerPartNode root = myPointers.get(pointer.getListener());
          int useCount = node.useCount;
          node.remove();
          FilePointerPartNode newNode =
              root.findPointerOrCreate(VfsUtilCore.urlToPath(urlAfter), 0, after);
          VirtualFilePointerImpl existingPointer = newNode.leaf;
          if (existingPointer != null) {
            // can happen when e.g. file renamed to the existing file
            // merge two pointers
            pointer.myNode = newNode;
          } else {
            newNode.associate(pointer, after);
          }
          newNode.incrementUsageCount(useCount);
        }
      }
    }

    VirtualFilePointer[] pointersToFireArray = toPointers(myPointersToFire);
    for (VirtualFilePointer pointer : pointersToFireArray) {
      ((VirtualFilePointerImpl) pointer).myNode.update();
    }

    for (EventDescriptor event : myEvents) {
      event.fireAfter();
    }

    if (pointersToFireArray.length != 0) {
      myBus.syncPublisher(VirtualFilePointerListener.TOPIC).validityChanged(pointersToFireArray);
    }

    myPointersToUpdateUrl = Collections.emptyList();
    myEvents = Collections.emptyList();
    myPointersToFire = Collections.emptyList();
    for (FilePointerPartNode root : myPointers.values()) {
      root.checkConsistency();
    }
  }
    @Override
    public void after(@NotNull final List<? extends VFileEvent> events) {
      cleanContainerCaches();

      if (myUrlsToUpdate == null) {
        return;
      }
      for (String url : myUrlsToUpdate) {
        synchronized (VirtualFilePointerManagerImpl.this) {
          for (TreeMap<String, VirtualFilePointerImpl> urlToPointer : myUrlToPointerMaps.values()) {
            VirtualFilePointerImpl pointer = urlToPointer.remove(url);
            if (pointer != null) {
              String path = VfsUtil.urlToPath(pointer.getUrl());
              urlToPointer.put(path, pointer);
            }
          }
        }
      }

      for (VirtualFilePointer pointer : myPointersToUpdate) {
        ((VirtualFilePointerImpl) pointer).update();
      }

      for (EventDescriptor event : myEvents) {
        event.fireAfter();
      }

      if (!myPointersToUpdate.isEmpty()) {
        VirtualFilePointer[] arr =
            myPointersToUpdate.toArray(new VirtualFilePointer[myPointersToUpdate.size()]);
        myBus.syncPublisher(VirtualFilePointerListener.TOPIC).validityChanged(arr);
      }

      myUrlsToUpdate = null;
      myEvents = null;
      myPointersToUpdate = null;
    }
    @Override
    public void before(@NotNull final List<? extends VFileEvent> events) {
      cleanContainerCaches();
      List<VirtualFilePointer> toFireEvents = new ArrayList<VirtualFilePointer>();
      List<String> toUpdateUrl = new ArrayList<String>();

      synchronized (VirtualFilePointerManagerImpl.this) {
        for (VFileEvent event : events) {
          if (event instanceof VFileDeleteEvent) {
            final VFileDeleteEvent deleteEvent = (VFileDeleteEvent) event;
            String url = deleteEvent.getFile().getPath();
            addPointersUnder(url, true, toFireEvents);
          } else if (event instanceof VFileCreateEvent) {
            final VFileCreateEvent createEvent = (VFileCreateEvent) event;
            String url = createEvent.getPath();
            addPointersUnder(url, false, toFireEvents);
          } else if (event instanceof VFileCopyEvent) {
            final VFileCopyEvent copyEvent = (VFileCopyEvent) event;
            String url = copyEvent.getNewParent().getPath() + "/" + copyEvent.getFile().getName();
            addPointersUnder(url, false, toFireEvents);
          } else if (event instanceof VFileMoveEvent) {
            final VFileMoveEvent moveEvent = (VFileMoveEvent) event;
            List<VirtualFilePointer> pointers = new ArrayList<VirtualFilePointer>();
            addPointersUnder(moveEvent.getFile().getPath(), false, pointers);
            for (VirtualFilePointer pointer : pointers) {
              VirtualFile file = pointer.getFile();
              if (file != null) {
                toUpdateUrl.add(file.getPath());
              }
            }
          } else if (event instanceof VFilePropertyChangeEvent) {
            final VFilePropertyChangeEvent change = (VFilePropertyChangeEvent) event;
            if (VirtualFile.PROP_NAME.equals(change.getPropertyName())) {
              List<VirtualFilePointer> pointers = new ArrayList<VirtualFilePointer>();
              addPointersUnder(change.getFile().getPath(), false, pointers);
              for (VirtualFilePointer pointer : pointers) {
                VirtualFile file = pointer.getFile();
                if (file != null) {
                  toUpdateUrl.add(file.getPath());
                }
              }
            }
          }
        }

        myEvents = new ArrayList<EventDescriptor>();
        for (VirtualFilePointerListener listener : myUrlToPointerMaps.keySet()) {
          if (listener == null) continue;
          EventDescriptor event = new EventDescriptor(listener, toFireEvents);
          myEvents.add(event);
        }
      }

      for (EventDescriptor event : myEvents) {
        event.fireBefore();
      }

      if (!toFireEvents.isEmpty()) {
        VirtualFilePointer[] arr =
            toFireEvents.toArray(new VirtualFilePointer[toFireEvents.size()]);
        myBus.syncPublisher(VirtualFilePointerListener.TOPIC).beforeValidityChanged(arr);
      }

      myPointersToUpdate = toFireEvents;
      myUrlsToUpdate = toUpdateUrl;
    }
  @Override
  public void before(@NotNull final List<? extends VFileEvent> events) {
    List<FilePointerPartNode> toFireEvents = new ArrayList<FilePointerPartNode>();
    List<FilePointerPartNode> toUpdateUrl = new ArrayList<FilePointerPartNode>();
    VirtualFilePointer[] toFirePointers;

    synchronized (this) {
      incModificationCount();
      for (VFileEvent event : events) {
        if (event instanceof VFileDeleteEvent) {
          final VFileDeleteEvent deleteEvent = (VFileDeleteEvent) event;
          addPointersUnder(deleteEvent.getFile(), false, "", toFireEvents);

        } else if (event instanceof VFileCreateEvent) {
          final VFileCreateEvent createEvent = (VFileCreateEvent) event;
          addPointersUnder(createEvent.getParent(), true, createEvent.getChildName(), toFireEvents);
        } else if (event instanceof VFileCopyEvent) {
          final VFileCopyEvent copyEvent = (VFileCopyEvent) event;
          addPointersUnder(
              copyEvent.getNewParent(), true, copyEvent.getFile().getName(), toFireEvents);
        } else if (event instanceof VFileMoveEvent) {
          final VFileMoveEvent moveEvent = (VFileMoveEvent) event;
          VirtualFile eventFile = moveEvent.getFile();
          addPointersUnder(moveEvent.getNewParent(), true, eventFile.getName(), toFireEvents);

          List<FilePointerPartNode> nodes = new ArrayList<FilePointerPartNode>();
          addPointersUnder(eventFile, false, "", nodes);
          for (FilePointerPartNode pair : nodes) {
            VirtualFile file = pair.leaf.getFile();
            if (file != null) {
              toUpdateUrl.add(pair);
            }
          }
        } else if (event instanceof VFilePropertyChangeEvent) {
          final VFilePropertyChangeEvent change = (VFilePropertyChangeEvent) event;
          if (VirtualFile.PROP_NAME.equals(change.getPropertyName())) {
            VirtualFile eventFile = change.getFile();
            VirtualFile parent = eventFile.getParent(); // e.g. for LightVirtualFiles
            addPointersUnder(parent, true, change.getNewValue().toString(), toFireEvents);

            List<FilePointerPartNode> nodes = new ArrayList<FilePointerPartNode>();
            addPointersUnder(eventFile, false, "", nodes);
            for (FilePointerPartNode pair : nodes) {
              VirtualFile file = pair.leaf.getFile();
              if (file != null) {
                toUpdateUrl.add(pair);
              }
            }
          }
        }
      }

      myEvents = new ArrayList<EventDescriptor>();
      toFirePointers = toPointers(toFireEvents);
      for (final VirtualFilePointerListener listener : myPointers.keySet()) {
        if (listener == null) continue;
        List<VirtualFilePointer> filtered =
            ContainerUtil.filter(
                toFirePointers,
                new Condition<VirtualFilePointer>() {
                  @Override
                  public boolean value(VirtualFilePointer pointer) {
                    return ((VirtualFilePointerImpl) pointer).getListener() == listener;
                  }
                });
        if (!filtered.isEmpty()) {
          EventDescriptor event =
              new EventDescriptor(
                  listener, filtered.toArray(new VirtualFilePointer[filtered.size()]));
          myEvents.add(event);
        }
      }
    }

    for (EventDescriptor descriptor : myEvents) {
      descriptor.fireBefore();
    }

    if (!toFireEvents.isEmpty()) {
      myBus.syncPublisher(VirtualFilePointerListener.TOPIC).beforeValidityChanged(toFirePointers);
    }

    myPointersToFire = toFireEvents;
    myPointersToUpdateUrl = toUpdateUrl;
  }