package com.revolsys.swing.tree;
import java.awt.Component;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JTree;
import javax.swing.TransferHandler.TransferSupport;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import com.revolsys.beans.ClassRegistry;
import com.revolsys.beans.NonWeakListener;
import com.revolsys.collection.NameProxy;
import com.revolsys.collection.Parent;
import com.revolsys.collection.iterator.IteratorEnumeration;
import com.revolsys.datatype.DataType;
import com.revolsys.datatype.DataTypes;
import com.revolsys.logging.Logs;
import com.revolsys.swing.Icons;
import com.revolsys.swing.menu.MenuFactory;
import com.revolsys.swing.parallel.Invoke;
import com.revolsys.swing.tree.dnd.TreePathListTransferable;
import com.revolsys.swing.tree.dnd.TreeTransferHandler;
import com.revolsys.swing.tree.node.ParentTreeNode;
import com.revolsys.swing.tree.node.file.PathTreeNode;
import com.revolsys.util.IconNameProxy;
import com.revolsys.util.JavaBeanUtil;
import com.revolsys.util.Property;
import com.revolsys.util.ToolTipProxy;
public class BaseTreeNode
implements TreeNode, Iterable<BaseTreeNode>, PropertyChangeListener, NonWeakListener {
private static final ClassRegistry<Function<Object, BaseTreeNode>> NODE_FACTORY_REGISTRY = new ClassRegistry<>();
private static final ClassRegistry<Icon> NODE_ICON_REGISTRY = new ClassRegistry<>();
static {
final Function<Parent<?>, BaseTreeNode> parentFatcory = ParentTreeNode::new;
addNodeFactory(Parent.class, parentFatcory);
final Function<Path, BaseTreeNode> pathFactory = PathTreeNode::newTreeNode;
addNodeFactory(Path.class, pathFactory);
addNodeFactory(BaseTreeNode.class, (object) -> {
return (BaseTreeNode)object;
});
}
@SuppressWarnings({
"rawtypes", "unchecked"
})
public static void addNodeFactory(final Class<?> clazz, final Function<?, BaseTreeNode> factory) {
NODE_FACTORY_REGISTRY.put(clazz, (Function)factory);
}
public static void addNodeFactory(final Class<?> clazz, final Function<?, BaseTreeNode> factory,
final Icon icon) {
addNodeFactory(clazz, factory);
addNodeIcon(clazz, icon);
}
public static void addNodeIcon(final Class<?> clazz, final Icon icon) {
NODE_ICON_REGISTRY.put(clazz, icon);
}
@SuppressWarnings("unchecked")
public static <V> V getUserData(final TreePath path) {
Object value = path.getLastPathComponent();
if (value instanceof BaseTreeNode) {
final BaseTreeNode node = (BaseTreeNode)value;
value = node.getUserData();
}
return (V)value;
}
public static BaseTreeNode newTreeNode(final Object object) {
final Function<Object, BaseTreeNode> factory = NODE_FACTORY_REGISTRY.find(object);
BaseTreeNode node;
if (factory == null) {
node = new BaseTreeNode(object);
} else {
node = factory.apply(object);
}
if (node != null && node.getIcon() == null) {
Icon icon = null;
if (object instanceof IconNameProxy) {
final IconNameProxy iconNameProxy = (IconNameProxy)object;
icon = Icons.getIcon(iconNameProxy);
}
if (icon == null) {
icon = NODE_ICON_REGISTRY.find(object);
}
node.setIcon(icon);
}
return node;
}
private boolean open;
private boolean allowsChildren;
private Icon disabledIcon;
private Icon icon;
private String name;
private BaseTreeNode parent;
private JTree tree;
private Object userObject;
private boolean userObjectInitialized = true;
private boolean visible = true;
public BaseTreeNode(final boolean allowsChildren) {
this(null, allowsChildren);
}
public BaseTreeNode(final Object userObject) {
this(userObject, false);
}
public BaseTreeNode(final Object userObject, final boolean allowsChildren) {
this.userObject = userObject;
this.allowsChildren = allowsChildren;
if (userObject instanceof NameProxy) {
final NameProxy nameProxy = (NameProxy)userObject;
this.name = nameProxy.getName();
} else if (userObject != null) {
this.name = DataTypes.toString(userObject);
}
}
protected int addChild(final int index, final Object child) {
throw new UnsupportedOperationException();
}
protected int addChild(final Object child) {
throw new UnsupportedOperationException();
}
protected void addListener() {
Property.addListener(this.userObject, this);
}
@SuppressWarnings("rawtypes")
@Override
public Enumeration children() {
return IteratorEnumeration.newEnumeration(getChildren());
}
protected void closeDo() {
}
public void collapse() {
collapseChildren();
final JTree tree = getTree();
if (tree != null) {
final TreePath treePath = getTreePath();
if (treePath != null) {
tree.collapsePath(treePath);
}
}
}
public void collapseChildren() {
for (final BaseTreeNode child : getChildren()) {
child.collapse();
}
}
public final void delete() {
try {
final List<BaseTreeNode> children = this.getChildren();
delete(children);
closeDo();
removeListener();
} catch (final Throwable e) {
Logs.error(this, "Error deleting tree node: " + getName(), e);
} finally {
this.parent = null;
this.name = "";
this.tree = null;
this.userObject = null;
}
}
protected void delete(final List<BaseTreeNode> children) {
for (final BaseTreeNode child : children) {
try {
child.delete();
} catch (final Throwable e) {
Logs.error(this, "Error deleting tree node: " + child, e);
}
}
}
public boolean dndImportData(final TransferSupport support, int index)
throws IOException, UnsupportedFlavorException {
if (!TreeTransferHandler.isDndNoneAction(support)) {
final Transferable transferable = support.getTransferable();
if (support.isDataFlavorSupported(TreePathListTransferable.FLAVOR)) {
final Object data = transferable.getTransferData(TreePathListTransferable.FLAVOR);
if (data instanceof TreePathListTransferable) {
final TreePathListTransferable pathTransferable = (TreePathListTransferable)data;
final List<TreePath> pathList = pathTransferable.getPaths();
for (final TreePath sourcePath : pathList) {
index = importData(support, pathTransferable, index, sourcePath);
}
}
return true;
}
}
return false;
}
@Override
public boolean equals(final Object object) {
final Object userObject = getUserObject();
if (object == null) {
return false;
} else if (object == this) {
return true;
} else if (object == userObject) {
return true;
} else if (object.getClass().equals(getClass())) {
final BaseTreeNode node = (BaseTreeNode)object;
final Object otherUserObject1 = node.getUserObject();
if (DataType.equal(userObject, otherUserObject1)) {
return true;
} else {
return false;
}
} else {
if (userObject != null && userObject.equals(object)) {
return true;
} else {
return false;
}
}
}
public void expand() {
Invoke.andWait(() -> {
final TreePath treePath = getTreePath();
expand(treePath);
});
}
public void expand(final List<?> path) {
if (path != null) {
Invoke.andWait(() -> {
final TreePath treePath = getTreePath(path);
expand(treePath);
});
}
}
public void expand(final TreePath treePath) {
Invoke.andWait(() -> {
final JTree tree = getTree();
if (tree != null) {
tree.expandPath(treePath);
}
});
}
public void expandChildren() {
Invoke.andWait(() -> {
expand();
for (final BaseTreeNode child : getChildren()) {
child.expand();
}
});
}
@Override
protected void finalize() throws Throwable {
delete();
}
@Override
public boolean getAllowsChildren() {
return isAllowsChildren();
}
public BaseTreeNode getChild(final Object item) {
for (final BaseTreeNode child : getChildren()) {
if (child.equals(item)) {
return child;
}
}
return null;
}
@Override
public BaseTreeNode getChildAt(final int index) {
final List<BaseTreeNode> children = getChildren();
final BaseTreeNode child = children.get(index);
return child;
}
protected List<Class<?>> getChildClasses() {
return Collections.emptyList();
}
@Override
public int getChildCount() {
final List<BaseTreeNode> children = getChildren();
final int size = children.size();
return size;
}
public List<BaseTreeNode> getChildren() {
return Collections.emptyList();
}
public Icon getDisabledIcon() {
if (this.disabledIcon == null && this.icon != null) {
this.disabledIcon = Icons.getDisabledIcon(this.icon);
}
return this.disabledIcon;
}
public Icon getIcon() {
return this.icon;
}
@Override
public int getIndex(final TreeNode node) {
final List<BaseTreeNode> children = getChildren();
return children.indexOf(node);
}
protected int getIndexOfChild(final BaseTreeNode abstractTreeNode, final Object child) {
if (child != null) {
final List<BaseTreeNode> children = getChildren();
for (int i = 0; i < children.size(); i++) {
final BaseTreeNode childNode = children.get(i);
if (childNode instanceof BaseTreeNode) {
final BaseTreeNode node = childNode;
if (child.equals(node.getUserObject())) {
return i;
}
}
}
}
return -1;
}
public MenuFactory getMenu() {
final Object object = getUserObject();
if (object == null) {
return null;
} else {
return MenuFactory.getMenu(object);
}
}
public String getName() {
return this.name;
}
@Override
public BaseTreeNode getParent() {
return this.parent;
}
@SuppressWarnings("unchecked")
public <V extends BaseTreeNode> V getParentNode() {
return (V)getParent();
}
@SuppressWarnings("unchecked")
public <V extends JTree> V getTree() {
if (this.tree == null) {
final BaseTreeNode parent = getParent();
if (parent == null) {
return null;
} else {
return parent.getTree();
}
} else {
return (V)this.tree;
}
}
public Component getTreeCellRendererComponent(final Component renderer, final JTree tree,
final Object value, final boolean selected, final boolean expanded, final boolean leaf,
final int row, final boolean hasFocus) {
final Icon icon = getIcon();
final Icon disabledIcon = getDisabledIcon();
if (renderer instanceof JLabel) {
final JLabel label = (JLabel)renderer;
if (icon == null) {
label.setIcon(null);
} else {
label.setIcon(icon);
}
label.setDisabledIcon(disabledIcon);
}
final Object userData = getUserData();
JComponent component = (JComponent)renderer;
if (userData instanceof ToolTipProxy) {
final ToolTipProxy toolTipProxy = (ToolTipProxy)userData;
final String toolTip = toolTipProxy.getToolTip();
component.setToolTipText(toolTip);
} else {
component.setToolTipText(null);
}
return renderer;
}
@SuppressWarnings("unchecked")
public <V extends TreeModel> V getTreeModel() {
final JTree tree = getTree();
if (tree == null) {
return null;
} else {
return (V)tree.getModel();
}
}
public TreePath getTreePath() {
final BaseTreeNode parent = getParent();
if (parent == null) {
return new TreePath(this);
} else {
final TreePath parentPath = parent.getTreePath();
return parentPath.pathByAddingChild(this);
}
}
public TreePath getTreePath(final List<?> path) {
if (path != null && !path.isEmpty()) {
final Object first = path.get(0);
if (equals(first)) {
final List<?> subPath = path.subList(1, path.size());
if (subPath.isEmpty()) {
return getTreePath();
} else {
final Object child = subPath.get(0);
final BaseTreeNode childNode = getChild(child);
if (childNode != null) {
return childNode.getTreePath(subPath);
}
}
}
}
return null;
}
public TreePath getTreePath(final Object item) {
final BaseTreeNode child = getChild(item);
if (child == null) {
return null;
} else {
return child.getTreePath();
}
}
@SuppressWarnings("unchecked")
public <V> V getUserData() {
return (V)this.userObject;
}
public final Object getUserObject() {
return this.userObject;
}
@Override
public int hashCode() {
final Object object = getUserObject();
if (object == null) {
return 1;
} else {
return object.hashCode();
}
}
protected int importData(final TransferSupport support,
final TreePathListTransferable pathTransferable, int index, final TreePath sourcePath) {
Object child = getUserData(sourcePath);
if (TreeTransferHandler.isDndCopyAction(support)) {
if (isCopySupported(child)) {
child = JavaBeanUtil.clone(child);
pathTransferable.addCopiedPath(sourcePath);
} else {
return index;
}
} else {
final int childIndex = getIndexOfChild(this, child);
if (childIndex > -1) {
removeChild(child);
pathTransferable.setSameParent(sourcePath);
if (index != -1) {
if (childIndex > -1 && childIndex < index) {
index--;
}
}
} else {
pathTransferable.addMovedPath(sourcePath);
}
}
if (index != -1) {
addChild(index, child);
index++;
} else {
addChild(child);
}
return index;
}
public boolean isAllowsChildren() {
return this.allowsChildren;
}
public boolean isChildrenInitialized() {
return true;
}
public boolean isCopySupported() {
return false;
}
public boolean isCopySupported(final Object child) {
return child instanceof Cloneable;
}
public boolean isDndCanImport(final TreePath dropPath, final TransferSupport support) {
if (support.isDataFlavorSupported(TreePathListTransferable.FLAVOR)) {
try {
final Object node = dropPath.getLastPathComponent();
if (node == this) {
final Transferable transferable = support.getTransferable();
final Object data = transferable.getTransferData(TreePathListTransferable.FLAVOR);
if (data instanceof TreePathListTransferable) {
final TreePathListTransferable pathTransferable = (TreePathListTransferable)data;
final List<TreePath> pathList = pathTransferable.getPaths();
for (final TreePath treePath : pathList) {
if (!isDndDropSupported(support, dropPath, treePath)) {
return false;
}
}
}
support.setShowDropLocation(true);
return true;
}
} catch (final Exception e) {
return false;
}
}
return false;
}
public boolean isDndDropSupported(final TransferSupport support, final TreePath dropPath,
final TreePath sourcePath) {
final boolean descendant = sourcePath.isDescendant(dropPath);
if (!descendant) {
final Object value = getUserData(sourcePath);
if (isDndDropSupported(support, dropPath, sourcePath, value)) {
return true;
}
}
return false;
}
protected boolean isDndDropSupported(final TransferSupport support, final TreePath dropPath,
final TreePath childPath, final Object child) {
final Class<?> valueClass = child.getClass();
final List<Class<?>> childClasses = getChildClasses();
for (final Class<?> supportedClass : childClasses) {
if (supportedClass.isAssignableFrom(valueClass)) {
if (TreeTransferHandler.isDndCopyAction(support)) {
if (!isCopySupported(child)) {
return false;
}
}
return true;
}
}
return false;
}
public boolean isExists() {
final BaseTreeNode parent = getParent();
if (parent == null) {
return true;
} else {
return parent.isExists();
}
}
@Override
public boolean isLeaf() {
return getChildCount() == 0;
}
public boolean isLoaded() {
return true;
}
public boolean isOpen() {
return this.open;
}
public boolean isUserObjectInitialized() {
return this.userObjectInitialized;
}
public boolean isVisible() {
return this.visible;
}
@Override
public Iterator<BaseTreeNode> iterator() {
final List<BaseTreeNode> children = getChildren();
return children.iterator();
}
public void mouseClicked(final MouseEvent e) {
}
public void mouseEntered(final MouseEvent e) {
}
public void mouseExited(final MouseEvent e) {
}
public void mousePressed(final MouseEvent e) {
}
public void mouseReleased(final MouseEvent e) {
}
public void nodeChanged() {
final TreeModel model = getTreeModel();
if (model instanceof DefaultTreeModel) {
Invoke.later(() -> {
final DefaultTreeModel treeModel = (DefaultTreeModel)model;
treeModel.nodeChanged(this);
});
}
}
public void nodeCollapsed(final BaseTreeNode treeNode) {
}
protected void nodeRemoved(final int index, final Object child) {
nodesRemoved(new int[] {
index
}, child);
}
protected void nodesChanged(final int... indicies) {
final TreeModel model = getTreeModel();
if (model instanceof DefaultTreeModel) {
final DefaultTreeModel treeModel = (DefaultTreeModel)model;
treeModel.nodesChanged(this, indicies);
}
}
protected void nodesInserted(final int... indicies) {
final TreeModel model = getTreeModel();
if (model instanceof DefaultTreeModel) {
final DefaultTreeModel treeModel = (DefaultTreeModel)model;
treeModel.nodesWereInserted(this, indicies);
}
}
protected void nodesRemoved(final int[] indicies, final Object... children) {
final TreeModel model = getTreeModel();
if (model instanceof DefaultTreeModel) {
final DefaultTreeModel treeModel = (DefaultTreeModel)model;
treeModel.nodesWereRemoved(this, indicies, children);
}
}
@Override
public final void propertyChange(final PropertyChangeEvent e) {
final Runnable action = () -> propertyChangeDo(e);
Invoke.later(action);
}
protected void propertyChangeDo(final PropertyChangeEvent e) {
final Object source = e.getSource();
final Object userObject = getUserObject();
if (source == userObject) {
final TreeModel model = getTreeModel();
if (model instanceof DefaultTreeModel) {
final DefaultTreeModel defaultModel = (DefaultTreeModel)model;
defaultModel.nodeChanged(this);
}
final String propertyName = e.getPropertyName();
if ("open".equals(propertyName)) {
if ((Boolean)e.getNewValue()) {
expand();
}
}
}
}
public boolean removeChild(final Object child) {
throw new UnsupportedOperationException();
}
public boolean removeChild(final TreePath path) {
final Object child = getUserData(path);
return removeChild(child);
}
protected void removeListener() {
Property.removeListener(this.userObject, this);
if (isLoaded()) {
for (final BaseTreeNode child : getChildren()) {
child.removeListener();
}
}
}
public void setAllowsChildren(final boolean allowsChildren) {
this.allowsChildren = allowsChildren;
}
protected void setDisabledIcon(final Icon disabledIcon) {
this.disabledIcon = disabledIcon;
}
protected void setIcon(final Icon icon) {
final Icon oldIcon = this.icon;
if (this.icon != icon) {
this.icon = icon;
if (oldIcon == null || icon == null || oldIcon.getIconWidth() != icon.getIconWidth()) {
nodeChanged();
}
}
}
public void setName(final String name) {
this.name = name;
}
public void setOpen(final boolean open) {
this.open = open;
}
public void setParent(final BaseTreeNode parent) {
if (parent == null) {
BaseTreeNodeLoadingIcon.removeNode(this);
removeListener();
} else {
addListener();
}
this.parent = parent;
}
void setTree(final JTree tree) {
this.tree = tree;
if (tree != null) {
addListener();
}
}
protected void setUserObject(final Object userObject) {
this.userObject = userObject;
}
protected void setUserObjectInitialized(final boolean userObjectInitialized) {
this.userObjectInitialized = userObjectInitialized;
}
public void setVisible(final boolean visible) {
this.visible = visible;
nodeChanged();
}
@Override
public String toString() {
if (Property.hasValue(this.name)) {
return this.name;
} else {
final Object userObject = getUserObject();
if (userObject == null) {
return "???";
} else {
return DataTypes.toString(userObject);
}
}
}
}