private void syncModelWithSwingNodes() {
    List<NodeChange> nodeChanges;
    synchronized (modelToSwingNodeChanges) {
      nodeChanges = new ArrayList<>(modelToSwingNodeChanges);
      modelToSwingNodeChanges.clear();
    }

    IntList indicesToFire = new IntArrayList();
    List<Node> nodesToFire = new ArrayList<>();

    //first grouping changes by parent node
    Map<Node, List<NodeChange>> groupByParent = nodeChanges.stream().collect(Collectors.groupingBy(NodeChange::getParentNode));
    for (Map.Entry<Node, List<NodeChange>> entry : groupByParent.entrySet()) {
      Node parentNode = entry.getKey();
      synchronized (parentNode) {
        List<NodeChange> changes = entry.getValue();
        List<NodeChange> addedToThisNode = new ArrayList<>();
        //removing node
        for (NodeChange change : changes) {
          if (change.nodeChangeType.equals(NodeChangeType.REMOVED) || change.nodeChangeType.equals(NodeChangeType.REPLACED)) {
            GroupNode grandParent = (GroupNode)parentNode.getParent();
            int index = grandParent.getSwingChildren().indexOf(parentNode);
            if (index >= 0) {
              grandParent.getSwingChildren().remove(parentNode);
              myModel.nodesWereRemoved(grandParent, new int[]{index}, new Object[]{parentNode});
              myModel.fireTreeStructureChanged(grandParent, myModel.getPathToRoot(parentNode), new int[]{index}, new Object[]{parentNode});
              if (parentNode instanceof UsageNode) {
                grandParent.incrementUsageCount(-1);
              }
              //if this node was removed than we can skip all the other changes related to it
              break;
            }
          }
          else {
            addedToThisNode.add(change);
          }
        }

        //adding children nodes in batch
        if (!addedToThisNode.isEmpty()) {
          for (NodeChange change : addedToThisNode) {
            Node childNode = change.childNode;
            if (childNode == null) {
              continue;
            }
            synchronized (childNode) {
              List<Node> swingChildren = ((GroupNode)parentNode).getSwingChildren();
              boolean contains = swingChildren.contains(childNode);
              if (!contains) {
                nodesToFire.add(childNode);

                parentNode.insertNewNode(childNode, 0);
                swingChildren.sort(COMPARATOR);
                indicesToFire.add(swingChildren.indexOf(change.childNode));

                if (childNode instanceof UsageNode) {
                  ((GroupNode)parentNode).incrementUsageCount(1);
                }
              }
            }
            if (!indicesToFire.isEmpty()) {
              myModel.fireTreeNodesInserted(parentNode, myModel.getPathToRoot(parentNode), indicesToFire.toIntArray(),
                                            nodesToFire.toArray(new Node[0]));
              indicesToFire.clear();
              nodesToFire.clear();
            }
          }
        }
      }
    }
  }
private void syncModelWithSwingNodes()