@NotNull
  @SuppressWarnings("Duplicates")
  private Collection<String> checkWatchable(
      String reportedPath, boolean isExact, boolean fastPath) {
    if (reportedPath == null) return Collections.emptyList();

    List<String> flatWatchRoots = myFlatWatchRoots;
    List<String> recursiveWatchRoots = myRecursiveWatchRoots;
    if (flatWatchRoots.isEmpty() && recursiveWatchRoots.isEmpty()) return Collections.emptyList();

    List<Pair<String, String>> mapping = myMapping;
    Collection<String> affectedPaths = new SmartList<String>(reportedPath);
    for (Pair<String, String> map : mapping) {
      if (FileUtil.startsWith(reportedPath, map.first)) {
        affectedPaths.add(map.second + reportedPath.substring(map.first.length()));
      } else if (FileUtil.startsWith(reportedPath, map.second)) {
        affectedPaths.add(map.first + reportedPath.substring(map.second.length()));
      }
    }

    Collection<String> changedPaths = new SmartList<String>();
    ext:
    for (String path : affectedPaths) {
      if (fastPath && !changedPaths.isEmpty()) break;

      for (String root : flatWatchRoots) {
        if (FileUtil.namesEqual(path, root)) {
          changedPaths.add(path);
          continue ext;
        }
        if (isExact) {
          String parentPath = new File(path).getParent();
          if (parentPath != null && FileUtil.namesEqual(parentPath, root)) {
            changedPaths.add(path);
            continue ext;
          }
        }
      }

      for (String root : recursiveWatchRoots) {
        if (FileUtil.startsWith(path, root)) {
          changedPaths.add(path);
          continue ext;
        }
        if (!isExact) {
          String parentPath = new File(root).getParent();
          if (parentPath != null && FileUtil.namesEqual(path, parentPath)) {
            changedPaths.add(root);
            continue ext;
          }
        }
      }
    }

    return changedPaths;
  }
    private void processChange(String path, WatcherOp op) {
      if (SystemInfo.isWindows
          && op == WatcherOp.RECDIRTY
          && path.length() == 3
          && Character.isLetter(path.charAt(0))) {
        VirtualFile root = LocalFileSystem.getInstance().findFileByPath(path);
        if (root != null) {
          myNotificationSink.notifyPathsRecursive(list(root.getPresentableUrl()));
        }
        notifyOnAnyEvent();
        return;
      }

      if (op == WatcherOp.CHANGE) {
        // collapse subsequent change file change notifications that happen once we copy large file,
        // this allows reduction of path checks at least 20% for Windows
        synchronized (myLock) {
          for (int i = 0; i < myLastChangedPaths.length; ++i) {
            int last = myLastChangedPathIndex - i - 1;
            if (last < 0) last += myLastChangedPaths.length;
            String lastChangedPath = myLastChangedPaths[last];
            if (lastChangedPath != null && lastChangedPath.equals(path)) {
              return;
            }
          }
          myLastChangedPaths[myLastChangedPathIndex++] = path;
          if (myLastChangedPathIndex == myLastChangedPaths.length) myLastChangedPathIndex = 0;
        }
      }

      int length = path.length();
      if (length > 1 && path.charAt(length - 1) == '/') path = path.substring(0, length - 1);
      boolean exactPath = op != WatcherOp.DIRTY && op != WatcherOp.RECDIRTY;
      Collection<String> paths = checkWatchable(path, exactPath, false);

      if (paths.isEmpty()) {
        if (LOG.isDebugEnabled()) {
          LOG.debug("Not watchable, filtered: " + path);
        }
        return;
      }

      switch (op) {
        case STATS:
        case CHANGE:
          myNotificationSink.notifyDirtyPaths(paths);
          break;

        case CREATE:
        case DELETE:
          myNotificationSink.notifyPathsCreatedOrDeleted(paths);
          break;

        case DIRTY:
          myNotificationSink.notifyDirtyDirectories(paths);
          break;

        case RECDIRTY:
          myNotificationSink.notifyPathsRecursive(paths);
          break;

        default:
          LOG.error("Unexpected op: " + op);
      }

      notifyOnAnyEvent();
    }