/******************************************************************************
* Copyright: GPL v3 *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
******************************************************************************/
package dba.gui;
import dba.gui.auxClasses.nodes.AttributeNode;
import dba.gui.auxClasses.nodes.DatabaseNode;
import dba.gui.auxClasses.nodes.FunctionalDependencyNode;
import dba.gui.auxClasses.nodes.RelationNode;
import dba.utils.TreeEnum;
import dbaCore.data.Attribute;
import dbaCore.data.Database;
import dbaCore.data.FunctionalDependency;
import dbaCore.data.RelationSchema;
import javax.swing.*;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
/**
* Class which extends JTree and implements methods for our usage
* (singleton)
*
* @author Andreas Freitag
*/
public class CustomTree extends JTree {
private DefaultMutableTreeNode lastSelectedNode;
private CustomTree tree;
private static CustomTree instance;
private List<Integer> expandedTreeObjects;
private boolean supressExpansionEvent;
/**
*
*/
private static final long serialVersionUID = -7671158844002979801L;
private CustomTree() {
super();
UIManager.put("PopupMenu.consumeEventOnClose", Boolean.FALSE);
tree = this;
this.addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
lastSelectedNode = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
}
});
expandedTreeObjects = new LinkedList<>();
// add expansion listener to preserve expansion state of each node
tree.addTreeExpansionListener(new TreeExpansionListener() {
@Override
public void treeExpanded(TreeExpansionEvent event) {
processTreeExpansion(event);
}
@Override
public void treeCollapsed(TreeExpansionEvent event) {
processTreeCollapse(event);
}
});
}
/**
* Getter for the singelton customtree (thread-safe)
*/
public synchronized static CustomTree getInstance() {
if (instance == null) {
synchronized (CustomTree.class) {
instance = new CustomTree();
}
}
return instance;
}
/**
* Get parent relation from current marked attribute or fd
*
* @return parent relation
*/
public RelationSchema getParentRelation() {
RelationSchema relation = (RelationSchema) ((RelationNode) ((DefaultMutableTreeNode) this.getSelectionPath()
.getLastPathComponent()).getParent()).getUserObject();
if (relation == null) {
return (RelationSchema) ((RelationNode) lastSelectedNode.getParent()).getUserObject();
}
return relation;
}
/**
* Get current marked attribute
*
* @return marked attribute
*/
public Attribute getAttribute() {
Attribute attribute = (Attribute) ((AttributeNode) this.getSelectionPath().getLastPathComponent()).getUserObject();
if (attribute == null) {
return (Attribute) lastSelectedNode.getUserObject();
}
return attribute;
}
/**
* Check if relationname is existing in DB
*
* @param relName Relation Name to check, if existing in database
* @return true if name exists or if name does not exist
*/
public boolean checkIfRelationExists(String relName) {
for (RelationSchema relation : getDatabase().getDatabase()) {
if (relation.getName().equalsIgnoreCase(relName)) {
return true;
}
}
return false;
}
/**
* Getter for the current database
*
* @return Database
*/
public Database getDatabase() {
Database database = (Database) ((DatabaseNode) this.getModel().getRoot()).getUserObject();
if (database == null) {
return (Database) lastSelectedNode.getUserObject();
}
return database;
}
/**
* Getter for the current marked FD
*
* @return Marked FD
*/
public FunctionalDependency getFd() {
FunctionalDependency fd = (FunctionalDependency) ((FunctionalDependencyNode) this.getSelectionPath()
.getLastPathComponent()).getUserObject();
if (fd == null) {
return (FunctionalDependency) lastSelectedNode.getUserObject();
}
return fd;
}
/**
* Get current marked relation
*
* @return current marked relation
*/
public RelationSchema getRelation() {
RelationSchema relation = (RelationSchema) ((RelationNode) this.getSelectionPath().getLastPathComponent())
.getUserObject();
if (relation == null) {
return (RelationSchema) lastSelectedNode.getUserObject();
}
return relation;
}
/**
* Get the new selected Node in the after performing an operation!
*
* @param type Operation which will be performed
* @return Rownumber for selected Node after operation
*/
public int getNewSelectedItem(TreeEnum type) {
int rowNr = 0;
switch (type) {
case DelAttribute:
case DelFD:
// Parent Relation
rowNr = tree.getRowForPath(new TreePath(((RelationNode) ((DefaultMutableTreeNode) this.getSelectionPath()
.getLastPathComponent()).getParent()).getPath()));
break;
case AddRelation:
case DelRelation:
case InspectDb:
case OptimizeDb:
// Database
rowNr = tree.getRowForPath(new TreePath(((DatabaseNode) this.getModel().getRoot()).getPath()));
break;
case RenameRel:
case EditRel:
case InspectRel:
case OptimizeRel:
case AddFD:
case AddAttribute:
// Relation
rowNr = tree.getRowForPath(new TreePath(((RelationNode) this.getSelectionPath().getLastPathComponent())
.getPath()));
break;
case RenameAttr:
case EditAttr:
case TogglePk:
case ToggleFk:
case SetConstraints:
// Attribute
rowNr = tree.getRowForPath(new TreePath(((AttributeNode) this.getSelectionPath().getLastPathComponent())
.getPath()));
break;
case EditFd:
// FD
rowNr = tree.getRowForPath(new TreePath(((FunctionalDependencyNode) this.getSelectionPath()
.getLastPathComponent()).getPath()));
break;
}
return rowNr;
}
/**
* Select a row in tree and scroll to it
*
* @param row Row which will be selected
*/
public void setSelectedItem(int row) {
tree.setSelectionRow(row);
tree.scrollRowToVisible(row);
// tree.requestFocus();
}
/**
* Check if a given Attribute name exists in given RelationSchema
*
* @param name Name of the Attribute
* @param relation RelationSchema which will be checked
* @return true/false
*/
public boolean checkIfAttrExists(String name, RelationSchema relation) {
for (Attribute attr : relation.getAttributes()) {
if (attr.getName().equalsIgnoreCase(name)) {
return true;
}
}
return false;
}
/**
* Select Node in tree by given object
*
* @param target Object which will be selected
*/
public void setSelectedNode(Object target) {
selectNode(tree.getModel().getRoot(), target);
}
private void selectNode(Object root, Object target) {
int cc;
cc = tree.getModel().getChildCount(root);
for (int i = 0; i < cc; i++) {
DefaultMutableTreeNode child = (DefaultMutableTreeNode) tree.getModel().getChild(root, i);
if (child.getUserObject() == target) {
TreeNode[] pathWithNodes = child.getPath();
TreePath path = new TreePath(pathWithNodes);
tree.setSelectionPath(path);
tree.scrollPathToVisible(path);
break;
} else {
selectNode(child, target);
}
}
}
private void restoreTreeNode(CustomTree tree, TreePath parent, DefaultMutableTreeNode treeNode) {
// Traverse down through the children
TreeNode node = (TreeNode) parent.getLastPathComponent();
if (node.getChildCount() >= 0) { // If the node has children?
// Create a child numerator over the node
Enumeration<?> en = node.children();
while (en.hasMoreElements()) { // While we have children
DefaultMutableTreeNode dmTreeNode = (DefaultMutableTreeNode) en.nextElement(); // Derive the node
TreePath path = parent.pathByAddingChild(dmTreeNode); // Derive
// the
// path
restoreTreeNode(tree, path, dmTreeNode); // Recursive call
// with new path
} // End While we have more children
} // End If the node has children?
// Nodes need to be expand from last branch node up
if (treeNode != null) { // If true, this is the root node - ignore
// it
if (treeNode instanceof RelationNode) {
if (expandedTreeObjects.contains(((RelationSchema) treeNode.getUserObject()).getOwnId())) {
tree.expandPath(parent); // et viola
}
}
}
}
private void processTreeExpansion(TreeExpansionEvent e) {
if (!supressExpansionEvent) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.getPath().getLastPathComponent();
if (node instanceof RelationNode) {
expandedTreeObjects.add(((RelationSchema) node.getUserObject()).getOwnId());
}
}
}
private void processTreeCollapse(TreeExpansionEvent e) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.getPath().getLastPathComponent();
if (node instanceof RelationNode) {
Integer schemaId = ((RelationSchema) node.getUserObject()).getOwnId();
if (expandedTreeObjects.contains(schemaId)) {
expandedTreeObjects.remove(schemaId);
}
}
}
/**
* Restore the expansion state as stored in the list
*/
public void restoreExpansionState() {
supressExpansionEvent = true;
((DefaultTreeModel) tree.getModel()).reload();
restoreTreeNode(tree, new TreePath(tree.getModel().getRoot()), null);
supressExpansionEvent = false; // Now we can go back to responding
// normally to expansion events
}
/**
* Expand a given relation node
*
* @param relation node which will be expanded
*/
public void expandNode(RelationSchema relation) {
if (!expandedTreeObjects.contains(relation.getOwnId())) {
expandedTreeObjects.add(relation.getOwnId());
}
}
}