Clone a JTree element with content

Asterios Raptis
3 min readJun 23, 2023
Trees, Wilderness, Nature image. Image by Joe from Pixabay
Image by Joe from Pixabay

This post is about a deep clone of a part in a JTree element with content. We will use as content the library gen-tree. For the swing world we will use the DefaultMutableTreeNode.

We will approach as first step get the selected tree node from the current JTree element. We assume that the user clicked with the mouse and retrieve the event source that will be a DefaultMutableTreeNode that have to be cloned and the containing user objects that is a BaseTreeNode from the library gen-tree.

There exists already a library ‘swing-tree-component’ for get the selected tree item in the extension class JTreeExtensions#getSelectedDefaultMutableTreeNode. The implementation is the following:

 public static <T extends DefaultMutableTreeNode> Optional<T> 
getSelectedDefaultMutableTreeNode(@NonNull JTree tree, int x, int y)
{
TreePath selectionPath = tree.getPathForLocation(x, y);
if (selectionPath == null)
{
return Optional.empty();
}
Object lastPathComponent = selectionPath.getLastPathComponent();
return Optional.of((T)lastPathComponent);
}

So for now we have the selected tree node that have to be cloned. Therefore we get the user object from the DefaultMutableTreeNode that is a BaseTreeNode object. The BaseTreeNode object has an id, so we need two visitor object for get the MaxIndex from the root tree node and also a reindex visitor. Also we need to clone the BaseTreeNode object. The source code looks as follows:

// get the selected tree node from the DefaultMutableTreeNode
BaseTreeNode<GenericTreeElement<List<MysticCryptEntryModelBean>>, Long> selectedTreeNode =
(BaseTreeNode<GenericTreeElement<List<MysticCryptEntryModelBean>>, Long>)selectedDefaultMutableTreeNode
.getUserObject();
// declare a visitor for reindex the new tree nodes
ReindexTreeNodeVisitor<GenericTreeElement<List<MysticCryptEntryModelBean>>, Long, BaseTreeNode<GenericTreeElement<List<MysticCryptEntryModelBean>>, Long>> reindexTreeNodeVisitor;

// declare a visitor for find the maximum index
MaxIndexFinderTreeNodeVisitor<GenericTreeElement<List<MysticCryptEntryModelBean>>, Long, BaseTreeNode<GenericTreeElement<List<MysticCryptEntryModelBean>>, Long>> maxIndexFinderTreeNodeVisitor;

Long maxIndex;
Long nextId;
// implement the visitor for find the max index
maxIndexFinderTreeNodeVisitor = new MaxIndexFinderTreeNodeVisitor<>()
{
@Override
public boolean isGreater(Long id)
{
return getMaxIndex() < id;
}
};
// clone the tree structure
BaseTreeNode<GenericTreeElement<List<MysticCryptEntryModelBean>>, Long> clonedTreeNode =
CloneQuietlyExtensions
.clone(selectedTreeNode);

As next step we need a new name for the cloned tree node and set the parent of the selected tree node. Then set all relevant values to the new cloned BaseTreeNode object and find the maximum index of the root tree node. This value we can use for create an id generator that will be used for the ReindexTreeNodeVisitor for reindex the cloned treenode. This process is done so all tree nodes have a unique id.


String newName = clonedTreeNode.getDisplayValue() + "-Copy";
// get parent
BaseTreeNode<GenericTreeElement<List<MysticCryptEntryModelBean>>, Long> parentTreeNode = selectedTreeNode
.getParent();
// set new name ...
clonedTreeNode.getValue().setName(newName);
clonedTreeNode.setDisplayValue(newName);
clonedTreeNode.setParent(parentTreeNode);

parentTreeNode.addChild(clonedTreeNode);
selectedTreeNode.getRoot().accept(maxIndexFinderTreeNodeVisitor);
maxIndex = maxIndexFinderTreeNodeVisitor.getMaxIndex();

nextId = maxIndex + 1;
LongIdGenerator idGenerator = LongIdGenerator.of(nextId);

reindexTreeNodeVisitor = new ReindexTreeNodeVisitor<>(idGenerator);
clonedTreeNode.accept(reindexTreeNodeVisitor);

For now we did the part of clone the BaseTreeNode object. For the swing part we have also to create a new DefaultMutableTreeNode object. Therefore we will get the parent from the selected DefaultMutableTreeNode object and use it for create the new DefaultMutableTreeNode object.


DefaultMutableTreeNode parent = (DefaultMutableTreeNode)selectedDefaultMutableTreeNode
.getParent();

BaseTreeNodeFactory.newDefaultMutableTreeNode(clonedTreeNode, parent, false);

The whole magic of cloning happens in the factory method newDefaultMutableTreeNode from the factory class BaseTreeNodeFactory. Here is what happens in the factory class BaseTreeNodeFactory:

 public static <T, K> DefaultMutableTreeNode newDefaultMutableTreeNode(
@NonNull BaseTreeNode<T, K> treeNode, DefaultMutableTreeNode parent, boolean onlyRoot)
{
BaseTreeNode<T, K> rootNode = treeNode;
if (onlyRoot && !treeNode.isRoot())
{
rootNode = treeNode.getRoot();
}
return traverseAndAdd(parent, rootNode, true);
}

public static <T, K> DefaultMutableTreeNode traverseAndAdd(
DefaultMutableTreeNode rootDefaultMutableTreeNode, @NonNull BaseTreeNode<T, K> treeNode,
boolean root)
{
DefaultMutableTreeNode parent = rootDefaultMutableTreeNode;
if (rootDefaultMutableTreeNode == null)
{
parent = DefaultMutableTreeNodeFactory.newDefaultMutableTreeNode(null, treeNode);
}
else
{
if (root)
{
parent = DefaultMutableTreeNodeFactory
.newDefaultMutableTreeNode(rootDefaultMutableTreeNode, treeNode);
}
}
for (final BaseTreeNode<T, K> data : treeNode.getChildren())
{
DefaultMutableTreeNode node = DefaultMutableTreeNodeFactory
.newDefaultMutableTreeNode(data);
parent.add(node);
traverseAndAdd(node, data, false);
}
return parent;
}

Thats it, we cloned successfully a DefaultMutableTreeNode object and all descentants.

All source code for this post is taken from mystic-crypt-ui(version 7.2) repository, gen-tree(version 8.3) repository and the swing-tree-component(version 2.5) are deployed under the MIT-License. So you can copy or modify and use it in private and in commercial projects or products.

Thanks for reading.

--

--

Asterios Raptis

Asterios Raptis is a Fullstack Developer and Software Consultant with over three decades of experience in the software development