/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.zookeeper.inspector.gui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import javax.swing.SwingWorker;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper.States;
import org.apache.zookeeper.inspector.ZooInspectorUtil;
import org.apache.zookeeper.inspector.manager.NodeListener;
import org.apache.zookeeper.inspector.manager.ZooInspectorManager;
import org.apache.zookeeper.inspector.toaster.Toaster;
// import com.nitido.utils.toaster.Toaster;
/**
* A {@link JPanel} for showing the tree view of all the nodes in the zookeeper
* instance
*/
public class ZooInspectorTreeViewer extends JPanel implements NodeListener,
TreeWillExpandListener {
private final ZooInspectorManager zooInspectorManager;
private final JTree tree;
private final Toaster toasterManager;
/**
* HACK:
* treeExpand event can be triggered other than mouse click, e.g. delete/add node
* in such cases we can skip cache refresh on expanded paths
*/
private final Set<String> skipRefreshPaths = Collections.synchronizedSet(new HashSet<String>());
private final ZooInspectorPanel zooInspectorPanel;
/**
* @param zooInspectorManager
* - the {@link ZooInspectorManager} for the application
* @param listener
* - the {@link TreeSelectionListener} to listen for changes in
* the selected node on the node tree
*/
public ZooInspectorTreeViewer(
final ZooInspectorPanel zooInspectorPanel,
final ZooInspectorManager zooInspectorManager,
TreeSelectionListener listener) {
this.zooInspectorPanel = zooInspectorPanel;
this.zooInspectorManager = zooInspectorManager;
this.setLayout(new BorderLayout());
final JPopupMenu popupMenu = new JPopupMenu();
final JMenuItem addNotify = new JMenuItem("Add Change Notification");
this.toasterManager = new Toaster();
this.toasterManager.setBorderColor(Color.BLACK);
this.toasterManager.setMessageColor(Color.BLACK);
this.toasterManager.setToasterColor(Color.WHITE);
addNotify.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("addNotify action_performed()");
List<String> selectedNodes = getSelectedNodes();
zooInspectorManager.addWatchers(selectedNodes,
ZooInspectorTreeViewer.this);
}
});
final JMenuItem removeNotify = new JMenuItem(
"Remove Change Notification");
removeNotify.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("removeNotify actionPerformed()");
List<String> selectedNodes = getSelectedNodes();
zooInspectorManager.removeWatchers(selectedNodes);
}
});
tree = new JTree(new DefaultMutableTreeNode());
System.out.println("init jtree: " + tree);
tree.setCellRenderer(new ZooInspectorTreeCellRenderer());
tree.setEditable(false);
tree.getSelectionModel().addTreeSelectionListener(listener);
tree.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.isPopupTrigger() || e.getButton() == MouseEvent.BUTTON3) {
// TODO only show add if a selected node isn't being
// watched, and only show remove if a selected node is being
// watched
popupMenu.removeAll();
popupMenu.add(addNotify);
popupMenu.add(removeNotify);
popupMenu.show(ZooInspectorTreeViewer.this, e.getX(), e
.getY());
}
}
});
tree.addTreeWillExpandListener(this);
this.add(tree, BorderLayout.CENTER);
}
@Override
public void treeWillExpand(TreeExpansionEvent event)
{
String znodePath = ZooInspectorUtil.treePathToZnodePath(event.getPath());
System.out.println("treeWillExpand invoked. willExpandPath: " + znodePath);
// System.out.println("Skip refresh " + skipRefreshPaths.size() + " paths");
if (skipRefreshPaths.contains(znodePath)) {
// System.out.println("Skip refresh path: " + znodePath);
} else {
try
{
zooInspectorManager.getCache().refresh(Arrays.asList(znodePath), 1);
}
catch (KeeperException e)
{
zooInspectorPanel.checkZookeeperStates(e.getMessage());
}
}
}
@Override
public void treeWillCollapse(TreeExpansionEvent event)
{
// TODO Auto-generated method stub
// System.out.println("collapsePath: " + event.getPath());
}
private List<String> getExpandedNodes()
{
List<String> expandedPaths = new ArrayList<String>();
int rowCount = tree.getRowCount();
for (int i = 0; i < rowCount; i++) {
TreePath path = tree.getPathForRow(i);
if (tree.isExpanded(path)) {
expandedPaths.add(ZooInspectorUtil.treePathToZnodePath(path));
}
}
return expandedPaths;
}
private void doRefresh(final TreePath[] selectedNodes) {
System.out.println("\tdoRefresh#selectedTreePaths: " + Arrays.toString(selectedNodes));
final Set<TreePath> expandedNodes = new LinkedHashSet<TreePath>();
int rowCount = tree.getRowCount();
for (int i = 0; i < rowCount; i++) {
TreePath path = tree.getPathForRow(i);
if (tree.isExpanded(path)) {
expandedNodes.add(path);
}
}
// final TreePath[] selectedNodes = tree.getSelectionPaths();
System.out.println("\texpandedNodes: " + expandedNodes);
SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() {
@Override
protected Boolean doInBackground() throws Exception {
tree.setModel(new DefaultTreeModel(new ZooInspectorTreeNode(
"/", null)));
return true;
}
@Override
protected void done() {
for (TreePath path : expandedNodes) {
tree.expandPath(path);
}
tree.getSelectionModel().setSelectionPaths(selectedNodes);
skipRefreshPaths.clear();
}
};
worker.execute();
}
/**
* Refresh the tree view
*/
public void refreshView() {
// reconnect if necessary
// if (zooInspectorManager.getZookeeperStates() == States.CLOSED) {
// System.out.println("ZooInspectorTreeViewer#refresh try reconnecting...");
// zooInspectorManager.connect(zooInspectorManager.getLastConnectionProps());
// }
// final Set<TreePath> expandedNodes = new LinkedHashSet<TreePath>();
List<String> visiblePaths = new ArrayList<String>();
// List<String> selectedPaths = new ArrayList<String>();
int rowCount = tree.getRowCount();
System.out.println("[START] ZooInspectorTreeViewer#refreshView invoked.");
System.out.println("\trowCount: " + rowCount);
for (int i = 0; i < rowCount; i++) {
TreePath path = tree.getPathForRow(i);
visiblePaths.add(ZooInspectorUtil.treePathToZnodePath(path));
// if (tree.isExpanded(path)) {
// expandedNodes.add(path);
// }
}
// final TreePath[] selectedNodes = tree.getSelectionPaths();
// System.out.println("\tvisiblePaths: " + visiblePaths);
// System.out.println("selectedNodes: " + selectedPaths);
try
{
zooInspectorManager.getCache().refresh(visiblePaths, 0);
}
catch (KeeperException e)
{
zooInspectorPanel.checkZookeeperStates(e.getMessage());
// shall skip refresh
return;
}
skipRefreshPaths.addAll(getExpandedNodes());
doRefresh(tree.getSelectionPaths());
System.out.println("[END] ZooInspectorTreeViewer#refreshView invoked.");
}
/**
* Refresh the tree view after delete nodes
* @param deletedNodes
*/
public void refreshViewAfterDelete(List<String> deletedNodes) {
System.out.println("deletedNodes: " + deletedNodes);
Set<String> expandedNodes = new HashSet<String>(getExpandedNodes());
for (String path : deletedNodes) {
expandedNodes.remove(path);
zooInspectorManager.getCache().removePrefix(path);
String parent = new File(path).getParent();
System.out.println("parent: " + parent);
try
{
zooInspectorManager.getCache().refresh(Arrays.asList(parent), 0);
}
catch (KeeperException e)
{
zooInspectorPanel.checkZookeeperStates(e.getMessage());
// shall skip refresh
return;
}
}
skipRefreshPaths.addAll(expandedNodes);
// modify selected nodes (that's deleted) to their parents
TreePath[] selectedNodes = tree.getSelectionPaths();
for (int i = 0; i < selectedNodes.length; i++) {
TreePath parent = selectedNodes[i].getParentPath();
selectedNodes[i] = parent;
}
doRefresh(selectedNodes);
}
/**
* Refresh the tree view after add a node under parent
* @param parent
* @param addNodeName
*/
public void refreshViewAfterAdd(String parent, String addNodeName) {
System.out.println("addNode. parent: " + parent + ", addNodeName: " + addNodeName);
try {
zooInspectorManager.getCache().refresh(Arrays.asList(parent.isEmpty()? "/" : parent), 0);
zooInspectorManager.getCache().refresh(Arrays.asList(parent + "/" + addNodeName), 0);
} catch (KeeperException e) {
zooInspectorPanel.checkZookeeperStates(e.getMessage());
// shall skip refresh
return;
}
skipRefreshPaths.addAll(getExpandedNodes());
doRefresh(tree.getSelectionPaths());
}
/**
* clear the tree view of all nodes
*/
public void clearView() {
tree.setModel(new DefaultTreeModel(new DefaultMutableTreeNode()));
}
/**
* @author Colin
*
*/
private static class ZooInspectorTreeCellRenderer extends
DefaultTreeCellRenderer {
public ZooInspectorTreeCellRenderer() {
// System.out.println("TreeRender() called");
setLeafIcon(ZooInspectorIconResources.getTreeLeafIcon());
setOpenIcon(ZooInspectorIconResources.getTreeOpenIcon());
setClosedIcon(ZooInspectorIconResources.getTreeClosedIcon());
}
}
/**
* @author Colin
*
*/
private class ZooInspectorTreeNode implements TreeNode {
private final String nodePath;
private final String nodeName;
private final ZooInspectorTreeNode parent;
public ZooInspectorTreeNode(String nodePath, ZooInspectorTreeNode parent) {
this.parent = parent;
this.nodePath = nodePath;
int index = nodePath.lastIndexOf("/");
if (index == -1) {
throw new IllegalArgumentException("Invalid node path"
+ nodePath);
}
this.nodeName = nodePath.substring(index + 1);
// System.out.println("init treeNode: " + nodePath + ", parent: " + parent);
}
/*
* (non-Javadoc)
*
* @see javax.swing.tree.TreeNode#children()
*/
@Override
public Enumeration<TreeNode> children() {
List<String> children = zooInspectorManager
.getChildren(this.nodePath);
// Collections.sort(children);
// System.out.println("sorted childs: " + children);
List<TreeNode> returnChildren = new ArrayList<TreeNode>();
for (String child : children) {
returnChildren.add(new ZooInspectorTreeNode((this.nodePath
.equals("/") ? "" : this.nodePath)
+ "/" + child, this));
}
return Collections.enumeration(returnChildren);
}
/*
* (non-Javadoc)
*
* @see javax.swing.tree.TreeNode#getAllowsChildren()
*/
@Override
public boolean getAllowsChildren() {
return zooInspectorManager.isAllowsChildren(this.nodePath);
}
/*
* (non-Javadoc)
*
* @see javax.swing.tree.TreeNode#getChildAt(int)
*/
@Override
public TreeNode getChildAt(int childIndex) {
String child = zooInspectorManager.getNodeChild(this.nodePath,
childIndex);
// System.out.println("getChildAt: " + childIndex + ", child: " + child);
if (child != null) {
return new ZooInspectorTreeNode((this.nodePath.equals("/") ? ""
: this.nodePath)
+ "/" + child, this);
}
return null;
}
/*
* (non-Javadoc)
*
* @see javax.swing.tree.TreeNode#getChildCount()
*/
@Override
public int getChildCount() {
return zooInspectorManager.getNumChildren(this.nodePath);
}
/*
* (non-Javadoc)
*
* @see javax.swing.tree.TreeNode#getIndex(javax.swing.tree.TreeNode)
*/
@Override
public int getIndex(TreeNode node) {
int idx = zooInspectorManager.getNodeIndex(this.nodePath);
System.out.println("getIndex: " + node + ", idx: " + idx);
return idx;
}
/*
* (non-Javadoc)
*
* @see javax.swing.tree.TreeNode#getParent()
*/
@Override
public TreeNode getParent() {
return this.parent;
}
/*
* (non-Javadoc)
*
* @see javax.swing.tree.TreeNode#isLeaf()
*/
@Override
public boolean isLeaf() {
return !zooInspectorManager.hasChildren(this.nodePath);
}
@Override
public String toString() {
return this.nodeName;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getOuterType().hashCode();
result = prime * result
+ ((nodePath == null) ? 0 : nodePath.hashCode());
result = prime * result
+ ((parent == null) ? 0 : parent.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ZooInspectorTreeNode other = (ZooInspectorTreeNode) obj;
if (!getOuterType().equals(other.getOuterType()))
return false;
if (nodePath == null) {
if (other.nodePath != null)
return false;
} else if (!nodePath.equals(other.nodePath))
return false;
if (parent == null) {
if (other.parent != null)
return false;
} else if (!parent.equals(other.parent))
return false;
return true;
}
private ZooInspectorTreeViewer getOuterType() {
return ZooInspectorTreeViewer.this;
}
}
/**
* @return {@link List} of the currently selected nodes
*/
public List<String> getSelectedNodes() {
TreePath[] paths = tree.getSelectionPaths();
List<String> selectedNodes = new ArrayList<String>();
if (paths != null) {
for (TreePath path : paths) {
StringBuilder sb = new StringBuilder();
Object[] pathArray = path.getPath();
for (Object o : pathArray) {
String nodeName = o.toString();
if (nodeName.length() > 0) {
sb.append("/");
sb.append(o.toString());
}
}
selectedNodes.add(sb.toString());
}
}
return selectedNodes;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.zookeeper.inspector.manager.NodeListener#processEvent(java
* .lang.String, java.lang.String, java.util.Map)
*/
@Override
public void processEvent(String nodePath, String eventType,
Map<String, String> eventInfo) {
StringBuilder sb = new StringBuilder();
sb.append("Node: ");
sb.append(nodePath);
sb.append("\nEvent: ");
sb.append(eventType);
if (eventInfo != null) {
for (Map.Entry<String, String> entry : eventInfo.entrySet()) {
sb.append("\n");
sb.append(entry.getKey());
sb.append(": ");
sb.append(entry.getValue());
}
}
this.toasterManager.showToaster(ZooInspectorIconResources
.getInformationIcon(), sb.toString());
}
}