package com.revolsys.swing.tree.node; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import com.revolsys.logging.Logs; import com.revolsys.swing.menu.MenuFactory; import com.revolsys.swing.parallel.Invoke; import com.revolsys.swing.tree.BaseTreeNode; import com.revolsys.swing.tree.TreeNodes; public abstract class LazyLoadTreeNode extends BaseTreeNode { public static void addRefreshMenuItem(final MenuFactory menu) { TreeNodes.addMenuItem(menu, "default", "Refresh", "arrow_refresh", LazyLoadTreeNode::refresh); } private List<BaseTreeNode> children = Collections.emptyList(); private boolean loaded = false; private final AtomicInteger updateIndicies = new AtomicInteger(); public LazyLoadTreeNode() { this(null); } public LazyLoadTreeNode(final Object userObject) { super(userObject, true); setLoading(); } protected void addNode(final int index, final BaseTreeNode node) { final List<BaseTreeNode> children = this.children; if (isLoaded()) { children.add(index, node); } } @Override protected void closeDo() { setLoading(); super.closeDo(); } @Override public void collapseChildren() { if (isLoaded()) { super.collapseChildren(); } } @Override public List<BaseTreeNode> getChildren() { return this.children; } protected int getUpdateIndex() { synchronized (this.updateIndicies) { return this.updateIndicies.incrementAndGet(); } } @Override public boolean isLoaded() { return this.loaded; } public void loadChildren() { if (!isLoaded()) { this.loaded = true; refresh(); } } protected List<BaseTreeNode> loadChildrenDo() { return new ArrayList<>(); } private List<BaseTreeNode> newLoadingNodes() { final List<BaseTreeNode> nodes = new ArrayList<>(); nodes.add(new LoadingTreeNode(this)); return nodes; } @Override public void nodeCollapsed(final BaseTreeNode treeNode) { super.nodeCollapsed(treeNode); if (treeNode != this) { final int updateIndex = getUpdateIndex(); setChildren(updateIndex, newLoadingNodes()); } } public final void refresh() { Invoke.background("Refresh tree nodes " + this.getName(), this::refreshDo); } protected synchronized void refreshDo() { try { final int updateIndex = getUpdateIndex(); List<BaseTreeNode> children = loadChildrenDo(); if (children == null) { children = Collections.emptyList(); } final List<BaseTreeNode> childNodes = children; Invoke.later(() -> setChildren(updateIndex, childNodes)); } catch (final Throwable e) { Logs.error(this, "Error refreshing: " + getName(), e); } } public final void removeNode(final BaseTreeNode node) { final List<BaseTreeNode> children = this.children; if (isLoaded()) { final int index = children.indexOf(node); removeNode(index); } } public final void removeNode(final int index) { final List<BaseTreeNode> children = this.children; if (isLoaded()) { if (index > 0 && index < children.size()) { final BaseTreeNode node = children.remove(index); nodeRemoved(index, node); } } } private void setChildren(final int updateIndex, final List<BaseTreeNode> newNodes) { if (updateIndex == this.updateIndicies.get()) { if (newNodes.size() == 1) { this.loaded = !(newNodes.get(0) instanceof LoadingTreeNode); } else { this.loaded = true; } final List<BaseTreeNode> oldNodes = this.children; for (int i = 0; i < oldNodes.size();) { final BaseTreeNode oldNode = oldNodes.get(i); if (newNodes.contains(oldNode)) { i++; } else { oldNodes.remove(i); nodeRemoved(i, oldNode); oldNode.setParent(null); } } for (int i = 0; i < newNodes.size();) { final BaseTreeNode oldNode; if (i < oldNodes.size()) { oldNode = oldNodes.get(i); } else { oldNode = null; } final BaseTreeNode newNode = newNodes.get(i); if (!newNode.equals(oldNode)) { newNode.setParent(this); oldNodes.add(i, newNode); nodesInserted(i); } i++; } } } private void setLoading() { this.children = newLoadingNodes(); this.loaded = false; } }