@Override
    void applyNodeToUiTree(Node loaded) {
      if (loaded == null || loaded.isLoadingRequired()) {
        if (LOG.isTraceEnabled()) LOG.debug("cancelled command: ", this);
        return;
      }
      if (node != tree.map.get(loaded.object)) {
        node.queue.close();
        LOG.warn("ignore removed node: " + node.object);
        return;
      }
      List<Node> oldChildren = node.getChildren();
      List<Node> newChildren = loaded.getChildren();
      if (tree.cachedPresentation != null) {
        tree.cachedPresentation.childrenLoaded(node.object, ContainerUtil.map(newChildren, child -> child.object));
      }
      if (oldChildren.isEmpty() && newChildren.isEmpty()) {
        node.setLeafState(loaded.leafState);
        treeNodesChanged(node, null);
        if (LOG.isTraceEnabled()) LOG.debug("no children: ", node.object);
        node.queue.done(this, node);
        return;
      }

      LinkedHashMap<Object, Integer> removed = getIndices(oldChildren, null);
      if (newChildren.isEmpty()) {
        oldChildren.forEach(child -> child.removeMapping(node, tree));
        node.setLeafState(loaded.leafState);
        treeNodesRemoved(node, removed);
        if (LOG.isTraceEnabled()) LOG.debug("children removed: ", node.object);
        node.queue.done(this, node);
        return;
      }

      // remove duplicated nodes during indices calculation
      ArrayList<Node> list = new ArrayList<>(newChildren.size());
      SmartHashSet<Object> reload = new SmartHashSet<>();
      LinkedHashMap<Object, Integer> inserted = getIndices(newChildren, child -> {
        Node found = tree.map.get(child.object);
        if (found == null) {
          tree.map.put(child.object, child);
          list.add(child);
        }
        else {
          tree.fixEqualButNotSame(found, child.object);
          list.add(found);
          if (found.leafState == LeafState.ALWAYS) {
            if (child.leafState != LeafState.ALWAYS) {
              found.setLeafState(child.leafState); // mark existing leaf node as not a leaf
              reload.add(found.object); // and request to load its children
            }
          }
          else if (child.leafState == LeafState.ALWAYS || !found.isLoadingRequired() && (deep || !removed.containsKey(found.object))) {
            reload.add(found.object); // request to load children of existing node
          }
        }
        return list.size() - 1;
      });
      newChildren = list;

      if (oldChildren.isEmpty()) {
        newChildren.forEach(child -> child.insertMapping(node));
        node.setChildren(newChildren);
        treeNodesInserted(node, inserted);
        if (LOG.isTraceEnabled()) LOG.debug("children inserted: ", node.object);
        node.queue.done(this, node);
        return;
      }

      LinkedHashMap<Object, Integer> contained = new LinkedHashMap<>();
      for (Object object : getIntersection(removed, inserted)) {
        Integer oldIndex = removed.remove(object);
        if (oldIndex == null) {
          LOG.warn("intersection failed");
        }
        Integer newIndex = inserted.remove(object);
        if (newIndex == null) {
          LOG.warn("intersection failed");
        }
        else {
          contained.put(object, newIndex);
        }
      }

      for (Node child : newChildren) {
        if (!removed.containsKey(child.object) && inserted.containsKey(child.object)) {
          child.insertMapping(node);
        }
      }

      for (Node child : oldChildren) {
        if (removed.containsKey(child.object) && !inserted.containsKey(child.object)) {
          child.removeMapping(node, tree);
        }
      }

      node.setChildren(newChildren);
      if (!removed.isEmpty()) treeNodesRemoved(node, removed);
      if (!inserted.isEmpty()) treeNodesInserted(node, inserted);
      if (!contained.isEmpty()) treeNodesChanged(node, contained);
      if (removed.isEmpty() && inserted.isEmpty() && contained.isEmpty()) treeNodesChanged(node, null);
      if (LOG.isTraceEnabled()) LOG.debug("children changed: ", node.object);

      if (!reload.isEmpty()) {
        for (Node child : newChildren) {
          if (reload.contains(child.object)) {
            submit(new CmdGetChildren("Update children recursively", child, true));
          }
        }
      }
      node.queue.done(this, node);
    }
@Override
    void applyNodeToUiTree(Node loaded)