@Override
    protected void process(@NotNull TreeModelEvent event, @NotNull EventType type) {
      TreePath path = event.getTreePath();
      if (path == null) {
        // request a new root from model according to the specification
        submit(new CmdGetRoot("Reload root", null));
        return;
      }
      Object object = path.getLastPathComponent();
      if (object == null) {
        LOG.warn("unsupported path: " + path);
        return;
      }
      if (path.getParentPath() == null && type == EventType.StructureChanged) {
        // set a new root object according to the specification
        submit(new CmdGetRoot("Update root", object));
        return;
      }
      onValidThread(() -> {
        Node node = tree.map.get(object);
        if (node == null) {
          if (LOG.isTraceEnabled()) LOG.debug("ignore updating of nonexistent node: ", object);
        }
        else if (type == EventType.NodesChanged) {
          // the object is already updated, so we should not start additional command to update
          AsyncTreeModel.this.treeNodesChanged(event.getTreePath(), event.getChildIndices(), event.getChildren());
        }
        else if (node.isLoadingRequired()) {
          // update the object presentation only, if its children are not requested yet
          AsyncTreeModel.this.treeNodesChanged(event.getTreePath(), null, null);
        }
        else if (type == EventType.NodesInserted) {
          submit(new CmdGetChildren("Insert children", node, false));
        }
        else if (type == EventType.NodesRemoved) {
          submit(new CmdGetChildren("Remove children", node, false));
        }
        else {
          submit(new CmdGetChildren("Update children", node, true));
        }
      });
    }
@Override
    protected void process(@NotNull TreeModelEvent event, @NotNull EventType type)