private static TreePath visitModel(@NotNull TreeModel model, @NotNull TreeVisitor visitor) {
    Object root = model.getRoot();
    if (root == null) return null;

    TreePath path = new CachingTreePath(root);
    switch (visitor.visit(path)) {
      case INTERRUPT -> {
        return path; // root path is found
      }
      case CONTINUE -> {
        // visit children
      }
      default -> {
        return null; // skip children
      }
    }
    Deque<Deque<TreePath>> stack = new ArrayDeque<>();
    stack.push(children(model, path));
    while (path != null) {
      Deque<TreePath> siblings = stack.peek();
      if (siblings == null) return null; // nothing to process

      TreePath next = siblings.poll();
      if (next == null) {
        LOG.assertTrue(siblings == stack.poll());
        path = path.getParentPath();
      }
      else {
        switch (visitor.visit(next)) {
          case INTERRUPT -> {
            return next; // path is found
          }
          case CONTINUE -> {
            path = next;
            stack.push(children(model, path));
          }
          case SKIP_SIBLINGS -> siblings.clear();
          case SKIP_CHILDREN -> {}
        }
      }
    }
    LOG.assertTrue(stack.isEmpty());
    return null;
  }
private static TreePath visitModel(@NotNull TreeModel model, @NotNull TreeVisitor visitor)