TreeUtil { @NotNull public static <T extends MutableTreeNode, U> List<T> updateChildrenNodes(@NotNull DefaultTreeModel treeModel, @NotNull T parentNode, @NotNull List<U> newEntries, @NotNull UpdateChildrenOps<T, U> ops) { if (newEntries.isEmpty()) { removeAllChildren(parentNode); treeModel.nodeStructureChanged(parentNode); return new ArrayList<>(); } else if (parentNode.getChildCount() == 0) { List<T> nodes = newEntries.stream().map(ops::mapEntry).collect(Collectors.toList()); removeAllChildren(parentNode); setAllowsChildren(parentNode); nodes.forEach(x -> parentNode.insert(x, parentNode.getChildCount())); treeModel.nodeStructureChanged(parentNode); return nodes; } else { List<T> addedNodes = new ArrayList<>(); int childIndex = 0; int childCount = parentNode.getChildCount(); int newEntryIndex = 0; int newEntryCount = newEntries.size(); while (newEntryIndex < newEntries.size() || childIndex < parentNode.getChildCount()) { assert childIndex == newEntryIndex; if (childIndex >= childCount) { T newEntryNode = ops.mapEntry(newEntries.get(newEntryIndex)); addedNodes.add(newEntryNode); setAllowsChildren(parentNode); treeModel.insertNodeInto(newEntryNode, parentNode, childIndex); newEntryIndex++; childIndex++; childCount++; continue; } if (newEntryIndex >= newEntryCount) { treeModel.removeNodeFromParent((MutableTreeNode)parentNode.getChildAt(childIndex)); childCount--; continue; } T childNode = ops.getChildNode(parentNode, childIndex); if (childNode == null) { treeModel.removeNodeFromParent((MutableTreeNode)parentNode.getChildAt(childIndex)); childCount--; continue; } int compareResult = ops.compareNodeWithEntry(childNode, newEntries.get(newEntryIndex)); if (compareResult == 0) { ops.updateNode(childNode, newEntries.get(newEntryIndex)); treeModel.nodeChanged(childNode); childIndex++; newEntryIndex++; continue; } if (compareResult < 0) { treeModel.removeNodeFromParent((MutableTreeNode)parentNode.getChildAt(childIndex)); childCount--; continue; } assert compareResult > 0; int nextIndex = findIndexOfNextEntry(newEntries, newEntryIndex + 1, childNode, ops); assert nextIndex >= newEntryIndex + 1; assert nextIndex <= newEntries.size(); for (; newEntryIndex < nextIndex; newEntryIndex++) { T newChildNode = ops.mapEntry(newEntries.get(newEntryIndex)); addedNodes.add(newChildNode); treeModel.insertNodeInto(newChildNode, parentNode, childIndex); childIndex++; childCount++; } } assert childIndex == newEntryIndex; assert childCount == newEntryCount; return addedNodes; } }  @NotNull static Stream<TreeNode> getChildren(@NotNull TreeNode node); @Nullable static TreePath getCommonPath(@NotNull List<V> treeNodes); static int binarySearch(@NotNull TreeNode parent, @NotNull TreeNode key, @NotNull Comparator<TreeNode> comparator); @NotNull static List<T> updateChildrenNodes(@NotNull DefaultTreeModel treeModel,
                                                                           @NotNull T parentNode,
                                                                           @NotNull List<U> newEntries,
                                                                           @NotNull UpdateChildrenOps<T, U> ops);  }
@Test public void testFullToEmpty() { myNewEntries.clear(); TreeUtil.updateChildrenNodes(myModel, myDirNode, myNewEntries, createDefaultOps()); assertSameEntries(myDirNode, myNewEntries); assertThat(myModelListener.getStructureChangedCount()).isEqualTo(1); assertThat(myModelListener.getNodesChangedCount()).isEqualTo(0); assertThat(myModelListener.getNodesInsertedCount()).isEqualTo(0); assertThat(myModelListener.getNodesRemovedCount()).isEqualTo(0); }
@Test public void testSingleInsertAtStartOfChildren() { myNewEntries.add(new Entry("a")); myNewEntries.sort(Comparator.comparing(Entry::getText)); TreeUtil.updateChildrenNodes(myModel, myDirNode, myNewEntries, createDefaultOps()); assertSameEntries(myDirNode, myNewEntries); assertThat(myModelListener.getStructureChangedCount()).isEqualTo(0); assertThat(myModelListener.getNodesChangedCount()).isEqualTo(myDirNode.getChildCount() - 1); assertThat(myModelListener.getNodesInsertedCount()).isEqualTo(1); assertThat(myModelListener.getNodesRemovedCount()).isEqualTo(0); }
@Test public void testSingleInsertAtEndOfChildren() { myNewEntries.add(new Entry("z")); myNewEntries.sort(Comparator.comparing(Entry::getText)); TreeUtil.updateChildrenNodes(myModel, myDirNode, myNewEntries, createDefaultOps()); assertSameEntries(myDirNode, myNewEntries); assertThat(myModelListener.getStructureChangedCount()).isEqualTo(0); assertThat(myModelListener.getNodesChangedCount()).isEqualTo(myDirNode.getChildCount() - 1); assertThat(myModelListener.getNodesInsertedCount()).isEqualTo(1); assertThat(myModelListener.getNodesRemovedCount()).isEqualTo(0); }
@Test public void testMultipleInserts() { myNewEntries.add(new Entry("a")); myNewEntries.add(new Entry("file2-1.txt")); myNewEntries.add(new Entry("file2-2.txt")); myNewEntries.add(new Entry("z")); myNewEntries.sort(Comparator.comparing(Entry::getText)); TreeUtil.updateChildrenNodes(myModel, myDirNode, myNewEntries, createDefaultOps()); assertSameEntries(myDirNode, myNewEntries); assertThat(myModelListener.getStructureChangedCount()).isEqualTo(0); assertThat(myModelListener.getNodesChangedCount()).isEqualTo(myDirNode.getChildCount() - 4); assertThat(myModelListener.getNodesInsertedCount()).isEqualTo(4); assertThat(myModelListener.getNodesRemovedCount()).isEqualTo(0); }
@Test public void testSingleDeleteAtStartOfChildren() { myNewEntries.remove(0); TreeUtil.updateChildrenNodes(myModel, myDirNode, myNewEntries, createDefaultOps()); assertSameEntries(myDirNode, myNewEntries); assertThat(myModelListener.getStructureChangedCount()).isEqualTo(0); assertThat(myModelListener.getNodesChangedCount()).isEqualTo(myDirNode.getChildCount()); assertThat(myModelListener.getNodesInsertedCount()).isEqualTo(0); assertThat(myModelListener.getNodesRemovedCount()).isEqualTo(1); }
@Test public void testSingleDeleteAtEndOfChildren() { myNewEntries.remove(myNewEntries.size() - 1); TreeUtil.updateChildrenNodes(myModel, myDirNode, myNewEntries, createDefaultOps()); assertSameEntries(myDirNode, myNewEntries); assertThat(myModelListener.getStructureChangedCount()).isEqualTo(0); assertThat(myModelListener.getNodesChangedCount()).isEqualTo(myDirNode.getChildCount()); assertThat(myModelListener.getNodesInsertedCount()).isEqualTo(0); assertThat(myModelListener.getNodesRemovedCount()).isEqualTo(1); }
@Test public void testMultipleDeletes() { myNewEntries.remove(0); myNewEntries.remove(3); TreeUtil.updateChildrenNodes(myModel, myDirNode, myNewEntries, createDefaultOps()); assertSameEntries(myDirNode, myNewEntries); assertThat(myModelListener.getStructureChangedCount()).isEqualTo(0); assertThat(myModelListener.getNodesChangedCount()).isEqualTo(myDirNode.getChildCount()); assertThat(myModelListener.getNodesInsertedCount()).isEqualTo(0); assertThat(myModelListener.getNodesRemovedCount()).isEqualTo(2); }
@Test public void testDeleteExtraNodes() { myDirNode.insert(new LoadingNode("test"), 2); TreeUtil.updateChildrenNodes(myModel, myDirNode, myNewEntries, createDefaultOps()); assertSameEntries(myDirNode, myNewEntries); assertThat(myModelListener.getStructureChangedCount()).isEqualTo(0); assertThat(myModelListener.getNodesChangedCount()).isEqualTo(myDirNode.getChildCount()); assertThat(myModelListener.getNodesInsertedCount()).isEqualTo(0); assertThat(myModelListener.getNodesRemovedCount()).isEqualTo(1); }