/***************************************************************************
* Copyright (C) 2008 by H-Store Project *
* Brown University *
* Massachusetts Institute of Technology *
* Yale University *
* *
* Permission is hereby granted, free of charge, to any person obtaining *
* a copy of this software and associated documentation files (the *
* "Software"), to deal in the Software without restriction, including *
* without limitation the rights to use, copy, modify, merge, publish, *
* distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to *
* the following conditions: *
* *
* The above copyright notice and this permission notice shall be *
* included in all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, *
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR *
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
* OTHER DEALINGS IN THE SOFTWARE. *
***************************************************************************/
package edu.brown.gui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.voltdb.catalog.Catalog;
import org.voltdb.catalog.CatalogType;
import org.voltdb.catalog.Cluster;
import org.voltdb.catalog.Database;
import org.voltdb.catalog.Host;
import org.voltdb.catalog.Procedure;
import org.voltdb.catalog.Table;
import org.voltdb.utils.Pair;
import edu.brown.catalog.conflicts.ConflictGraph;
import edu.brown.graphs.GraphvizExport;
import edu.brown.gui.catalog.AttributesNode;
import edu.brown.gui.catalog.CatalogAttributeText;
import edu.brown.gui.catalog.CatalogMapTreeNode;
import edu.brown.gui.catalog.CatalogSummaryUtil;
import edu.brown.gui.catalog.CatalogTreeModel;
import edu.brown.gui.catalog.PlanTreeCatalogNode;
import edu.brown.gui.catalog.ProcedureConflictGraphNode;
import edu.brown.gui.catalog.WrapperNode;
import edu.brown.utils.ArgumentsParser;
import edu.brown.utils.FileUtil;
import edu.brown.utils.IOFileFilter;
import edu.brown.utils.StringUtil;
/**
* Graphical Catalog Viewer Tool
* @author pavlo
*/
public class CatalogViewer extends AbstractViewer {
private static final long serialVersionUID = -7566054105094378095L;
protected static final String WINDOW_TITLE = "H-Store Catalog Browser";
// ----------------------------------------------
// CONTROL OPTIONS
// ----------------------------------------------
protected static final int SEARCH_MIN_LENGTH = 4;
// ----------------------------------------------
// CATALOG ELEMENTS
// ----------------------------------------------
protected Catalog catalog;
protected File catalog_file_path;
protected String catalog_path;
protected boolean text_mode = true;
// ----------------------------------------------
// GUI ELEMENTS
// ----------------------------------------------
protected CatalogTreeModel catalogTreeModel;
protected JTree catalogTree;
protected JTextField searchField;
protected JTextArea textInfoTextArea;
protected JScrollPane textInfoScroller;
protected JPanel textInfoPanel;
protected JPanel mainPanel;
protected CatalogSummaryUtil summaryUtil;
protected CatalogAttributeText attributeText;
// ----------------------------------------------
// MENU OPTIONS
// ----------------------------------------------
public enum MenuOptions {
CATALOG_OPEN_FILE,
CATALOG_OPEN_JAR,
CATALOG_SAVE,
CONFLICTGRAPH_EXPORT,
QUIT,
};
/**
*
* @param catalog
* @param catalog_path
*/
public CatalogViewer(ArgumentsParser args) {
super(args,
String.format("%s [%s]", WINDOW_TITLE, args.catalog_path),
DEFAULT_WINDOW_WIDTH,
DEFAULT_WINDOW_HEIGHT);
this.catalog = args.catalog;
this.catalog_file_path = args.catalog_path;
this.catalog_path = args.catalog_path.getAbsolutePath();
this.menuHandler = new CatalogViewer.MenuHandler();
this.init();
}
/**
*
* @param catalog
* @param catalog_path
*/
public CatalogViewer(Catalog catalog, String catalog_path) {
super(new ArgumentsParser(), WINDOW_TITLE);
this.catalog = catalog;
this.catalog_path = catalog_path;
this.menuHandler = new CatalogViewer.MenuHandler();
this.init();
}
public static void show(final Catalog catalog, final String catalog_path) {
CatalogViewer viewer = new CatalogViewer(catalog, catalog_path);
viewer.setVisible(true);
}
/**
* @param args
*/
public static void main(final String[] vargs) throws Exception {
final ArgumentsParser args = ArgumentsParser.load(vargs);
args.require(ArgumentsParser.PARAM_CATALOG);
// Procedure catalog_proc = args.catalog_db.getProcedures().get("slev");
// Statement catalog_stmt = catalog_proc.getStatements().get("GetStockCount");
// String jsonString = Encoder.hexDecodeToString(catalog_stmt.getMs_fullplan());
// JSONObject jsonObject = new JSONObject(jsonString);
// System.err.println(jsonObject.toString(2));
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
CatalogViewer viewer = new CatalogViewer(args);
viewer.setVisible(true);
} // RUN
});
}
private void generateCatalogTree(Catalog catalog, String catalog_path) {
this.catalogTreeModel = new CatalogTreeModel(this.args, catalog, catalog_path);
this.catalogTree.setModel(this.catalogTreeModel);
this.catalog = catalog;
this.catalog_path = catalog_path;
//
// Expand clusters and databases
//
this.catalogTree.expandPath(new TreePath(this.catalogTreeModel.getTablesNode().getPath()));
this.catalogTree.expandPath(new TreePath(this.catalogTreeModel.getProceduresNode().getPath()));
}
/**
*
*/
protected void viewerInit() {
this.summaryUtil = new CatalogSummaryUtil(this.catalog);
this.attributeText = new CatalogAttributeText(this.catalog);
// ----------------------------------------------
// MENU
// ----------------------------------------------
JMenu menu;
JMenuItem menuItem;
//
// File Menu
//
menu = new JMenu("File");
menu.getPopupMenu().setLightWeightPopupEnabled(false);
menu.setMnemonic(KeyEvent.VK_F);
menu.getAccessibleContext().setAccessibleDescription("File Menu");
menuBar.add(menu);
menuItem = new JMenuItem("Open Catalog From File");
menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK));
menuItem.getAccessibleContext().setAccessibleDescription("Open Catalog From File");
menuItem.addActionListener(this.menuHandler);
menuItem.putClientProperty(MenuHandler.MENU_ID, MenuOptions.CATALOG_OPEN_FILE);
menu.add(menuItem);
menuItem = new JMenuItem("Open Catalog From Jar");
menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK));
menuItem.getAccessibleContext().setAccessibleDescription("Open Catalog From Project Jar");
menuItem.addActionListener(this.menuHandler);
menuItem.putClientProperty(MenuHandler.MENU_ID, MenuOptions.CATALOG_OPEN_JAR);
menu.add(menuItem);
menu.addSeparator();
menuItem = new JMenuItem("Export ConflictGraph");
// menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK));
menuItem.getAccessibleContext().setAccessibleDescription("Export ConflictGraph to a Graphviz Dot File");
menuItem.addActionListener(this.menuHandler);
menuItem.putClientProperty(MenuHandler.MENU_ID, MenuOptions.CONFLICTGRAPH_EXPORT);
menu.add(menuItem);
menu.addSeparator();
menuItem = new JMenuItem("Quit", KeyEvent.VK_Q);
menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.CTRL_MASK));
menuItem.getAccessibleContext().setAccessibleDescription("Quit Program");
menuItem.addActionListener(this.menuHandler);
menuItem.putClientProperty(MenuHandler.MENU_ID, MenuOptions.QUIT);
menu.add(menuItem);
// ----------------------------------------------
// CATALOG TREE PANEL
// ----------------------------------------------
this.catalogTree = new JTree();
this.catalogTree.setEditable(false);
this.catalogTree.setCellRenderer(new CatalogViewer.CatalogTreeRenderer());
this.catalogTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
this.catalogTree.addTreeSelectionListener(new CatalogTreeSeleciontListener());
this.generateCatalogTree(this.catalog, this.catalog_file_path.getName());
// ----------------------------------------------
// TEXT INFORMATION PANEL
// ----------------------------------------------
this.textInfoPanel = new JPanel();
this.textInfoPanel.setLayout(new BorderLayout());
this.textInfoTextArea = new JTextArea();
this.textInfoTextArea.setEditable(false);
this.textInfoTextArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
this.textInfoTextArea.setText(StringUtil.header("CATALOG SUMMARY", "-", 50) + // HACK
"\n\n" + this.summaryUtil.getSummaryText());
this.textInfoTextArea.addFocusListener(new FocusListener() {
@Override
public void focusLost(FocusEvent e) {
// TODO Auto-generated method stub
}
@Override
public void focusGained(FocusEvent e) {
CatalogViewer.this.scrollTextInfoToTop();
}
});
this.textInfoScroller = new JScrollPane(this.textInfoTextArea);
this.textInfoPanel.add(this.textInfoScroller, BorderLayout.CENTER);
this.mainPanel = new JPanel(new BorderLayout());
this.mainPanel.add(textInfoPanel, BorderLayout.CENTER);
// ----------------------------------------------
// SEARCH TOOLBAR
// ----------------------------------------------
JPanel searchPanel = new JPanel();
searchPanel.setLayout(new BorderLayout());
JPanel innerSearchPanel = new JPanel();
innerSearchPanel.setLayout(new BoxLayout(innerSearchPanel, BoxLayout.X_AXIS));
innerSearchPanel.add(new JLabel("Search: "));
this.searchField = new JTextField(30);
innerSearchPanel.add(this.searchField);
searchPanel.add(innerSearchPanel, BorderLayout.EAST);
this.searchField.addKeyListener(new KeyListener() {
private String last = null;
@Override
public void keyReleased(KeyEvent e) {
String value = CatalogViewer.this.searchField.getText().toLowerCase().trim();
if (!value.isEmpty() && (this.last == null || !this.last.equals(value))) {
CatalogViewer.this.search(value);
}
this.last = value;
}
@Override
public void keyTyped(KeyEvent e) {
// Do nothing...
}
@Override
public void keyPressed(KeyEvent e) {
// Do nothing...
}
});
// Putting it all together
JScrollPane scrollPane = new JScrollPane(this.catalogTree);
JPanel topPanel = new JPanel();
topPanel.setLayout(new BorderLayout());
topPanel.add(searchPanel, BorderLayout.NORTH);
topPanel.add(scrollPane, BorderLayout.CENTER);
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, topPanel, this.mainPanel);
splitPane.setDividerLocation(400);
this.add(splitPane, BorderLayout.CENTER);
}
protected void replaceMainPanel(JPanel newPanel) {
this.mainPanel.remove(0);
this.mainPanel.add(newPanel, BorderLayout.CENTER);
this.mainPanel.validate();
this.mainPanel.repaint();
}
protected void search(String value) {
LOG.debug("Searching based on keyword '" + value + "'...");
Set<DefaultMutableTreeNode> found = new HashSet<DefaultMutableTreeNode>();
Integer guid = null;
// If it starts with "guid=", then that's a PlanColumn!
if (value.startsWith("guid=")) {
try {
guid = Integer.valueOf(value.split("guid=")[1]);
if (this.catalogTreeModel.getPlanNodeGuidNodeXref().containsKey(guid)) {
found.addAll(this.catalogTreeModel.getPlanNodeGuidNodeXref().get(guid));
} else {
guid = null;
}
} catch (Exception ex) {
// Ignore...
}
} else {
// Check if it's a digit, which means that they are searching by Catalog guid
try {
guid = Integer.parseInt(value);
if (this.catalogTreeModel.getGuidNodeXref().containsKey(guid)) {
found.add(this.catalogTreeModel.getGuidNodeXref().get(guid));
} else {
guid = null;
}
} catch (Exception ex) {
// Ignore...
}
}
// Otherwise search by name...
if (guid == null && value.length() >= SEARCH_MIN_LENGTH) {
LOG.debug("Searching for matching name '" + value + "'");
for (String name : this.catalogTreeModel.getNameNodeXref().keySet()) {
if (name.indexOf(value) != -1) {
found.addAll(this.catalogTreeModel.getNameNodeXref().get(name));
}
} // FOR
}
// Display found matches
if (!found.isEmpty()) {
this.highlight(found);
this.searchField.requestFocus();
}
}
protected void highlight(Collection<DefaultMutableTreeNode> nodes) {
// Collapse everything and then show the paths to each node
for (int ctr = this.catalogTree.getRowCount(); ctr >= 0; ctr--) {
if (this.catalogTree.isExpanded(ctr)) {
this.catalogTree.collapseRow(ctr);
}
} // FOR
this.catalogTree.getSelectionModel().clearSelection();
for (DefaultMutableTreeNode node : nodes) {
TreePath path = new TreePath(node.getPath());
this.catalogTree.setSelectionPath(path);
this.catalogTree.expandPath(path);
} // FOR
}
/**
* Scroll the attributes pane to the top
*/
protected void scrollTextInfoToTop() {
JScrollBar verticalScrollBar = this.textInfoScroller.getVerticalScrollBar();
JScrollBar horizontalScrollBar = this.textInfoScroller.getHorizontalScrollBar();
verticalScrollBar.setValue(verticalScrollBar.getMinimum());
horizontalScrollBar.setValue(horizontalScrollBar.getMinimum());
// System.err.println("VERTICAL=" + verticalScrollBar.getValue() + ", HORIZONTAL=" + horizontalScrollBar.getValue());
}
public class CatalogTreeRenderer extends DefaultTreeCellRenderer {
private static final long serialVersionUID = 1L;
/**
*
*/
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
if (value instanceof DefaultMutableTreeNode) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
Object obj = node.getUserObject();
if (obj instanceof WrapperNode) {
CatalogType catalog_obj = ((WrapperNode)node.getUserObject()).getCatalogType();
this.setFont(this.getFont().deriveFont(Font.BOLD));
//
// Cluster
//
if (catalog_obj instanceof Cluster) {
this.setIcon(UIManager.getIcon("FileView.computerIcon"));
//
// Database
//
} else if (catalog_obj instanceof Database) {
this.setIcon(UIManager.getIcon("FileView.hardDriveIcon"));
}
}
else if (obj instanceof ProcedureConflictGraphNode) {
this.setForeground(new Color(0, 0, 200));
this.setFont(this.getFont().deriveFont(Font.BOLD | Font.ITALIC));
this.setIcon(UIManager.getIcon("FileChooser.listViewIcon"));
}
else {
this.setFont(this.getFont().deriveFont(Font.PLAIN));
}
}
if (CatalogViewer.this.catalogTreeModel != null) {
//
// Root Node
//
Object root_node = CatalogViewer.this.catalogTreeModel.getRoot();
if (root_node != null && root_node == value) {
this.setIcon(UIManager.getIcon("FileView.floppyDriveIcon"));
this.setFont(this.getFont().deriveFont(Font.BOLD));
}
}
/*
if (leaf) {
this.setIcon(UIManager.getIcon("FileChooser.detailsViewIcon"));
}
*/
return (this);
}
} // END CLASS
protected String exportConflictGraph() {
IOFileFilter filter = new IOFileFilter("Graphviz Dot File", "dot");
File defaultFile = new File(String.format("%s-conflict.dot", args.catalogContext.database.getProject()));
String path = null;
try {
path = showSaveDialog("Export ConflictGraph", ".", filter, defaultFile);
if (path != null) {
ConflictGraph graph = this.catalogTreeModel.getProcedureConflictGraphNode().getConflictGraph();
assert(graph != null) : "Unexpected null ConflictGraph";
String serialized = GraphvizExport.export(graph, args.catalogContext.database.getProject());
FileUtil.writeStringToFile(new File(path), serialized);
}
} catch (Exception ex) {
ex.printStackTrace();
showErrorDialog("Failed to export ConflictGraph file", ex.getMessage());
}
return (path);
}
private class CatalogTreeSeleciontListener implements TreeSelectionListener {
public void valueChanged(TreeSelectionEvent e) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)CatalogViewer.this.catalogTree.getLastSelectedPathComponent();
if (node == null) return;
Object user_obj = node.getUserObject();
String new_header = null;
String new_text = null;
boolean text_mode = true;
if (user_obj instanceof WrapperNode) {
CatalogType catalog_obj = ((WrapperNode)user_obj).getCatalogType();
new_text = CatalogViewer.this.attributeText.getAttributesText(catalog_obj);
}
else if (user_obj instanceof AttributesNode) {
AttributesNode wrapper = (AttributesNode)user_obj;
new_text = wrapper.getAttributes();
}
else if (user_obj instanceof ProcedureConflictGraphNode) {
ProcedureConflictGraphNode wrapper = (ProcedureConflictGraphNode)user_obj;
CatalogViewer.this.replaceMainPanel(wrapper.getVisualization());
text_mode = false;
}
else if (user_obj instanceof PlanTreeCatalogNode) {
final PlanTreeCatalogNode wrapper = (PlanTreeCatalogNode)user_obj;
text_mode = false;
CatalogViewer.this.replaceMainPanel(wrapper.getPanel());
if (SwingUtilities.isEventDispatchThread() == false) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
wrapper.centerOnRoot();
}
});
} else {
wrapper.centerOnRoot();
}
}
// SUMMARY NODES
else if (node instanceof CatalogMapTreeNode) {
Class<? extends CatalogType> catalogType = ((CatalogMapTreeNode)node).getCatalogType();
if (catalogType.equals(Procedure.class)) {
new_text = StringUtil.formatMaps(summaryUtil.getProceduresInfo(true));
}
else if (catalogType.equals(Table.class)) {
new_text = StringUtil.formatMaps(summaryUtil.getTablesInfo(true));
}
else if (catalogType.equals(Host.class)) {
new_text = StringUtil.formatMaps(summaryUtil.getHostsInfo(true));
}
if (new_text != null) {
new_header = catalogType.getSimpleName() + "s Summary";
}
}
// EVERYTHING ELSE
if (text_mode && new_text == null) {
new_header = "Catalog Summary";
new_text = CatalogViewer.this.summaryUtil.getSummaryText();
}
// Text Mode
if (text_mode) {
if (CatalogViewer.this.text_mode == false) {
CatalogViewer.this.replaceMainPanel(CatalogViewer.this.textInfoPanel);
}
if (new_header != null) {
new_text = StringUtil.header(new_header.toUpperCase(), "-", 50) +
"\n\n" + new_text;
}
CatalogViewer.this.textInfoTextArea.setText(new_text);
// Scroll to top
CatalogViewer.this.textInfoTextArea.grabFocus();
}
CatalogViewer.this.text_mode = text_mode;
}
} // END CLASS
protected class MenuHandler extends AbstractMenuHandler {
/**
*
*/
@Override
public void actionPerformed(ActionEvent e) {
JMenuItem source = (JMenuItem)(e.getSource());
//
// Process the event
//
MenuOptions opt = MenuOptions.valueOf(source.getClientProperty(MENU_ID).toString());
switch (opt) {
// --------------------------------------------------------
// OPEN CATALOG FILE
// --------------------------------------------------------
case CATALOG_OPEN_FILE: {
Pair<Catalog, String> result = openCatalogFile();
if (result != null) {
generateCatalogTree(result.getFirst(), result.getSecond());
}
break;
}
// --------------------------------------------------------
// OPEN CATALOG JAR
// --------------------------------------------------------
case CATALOG_OPEN_JAR: {
Pair<Catalog, String> result = openCatalogJar();
if (result != null) {
generateCatalogTree(result.getFirst(), result.getSecond());
}
break;
}
// --------------------------------------------------------
// EXPORT CONFLICT GRAPH
// --------------------------------------------------------
case CONFLICTGRAPH_EXPORT: {
String path = exportConflictGraph();
if (path != null) {
LOG.info("Exported ConflictGraph to '" + path + "'");
}
break;
}
// --------------------------------------------------------
// QUIT
// --------------------------------------------------------
case QUIT: {
quit();
break;
}
// --------------------------------------------------------
// UNKNOWN
// --------------------------------------------------------
default:
System.err.println("Invalid Menu Action: " + source.getName());
} // SWITCH
}
} // END CLASS
}