package pipe.gui;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.ClassPath;
import pipe.constants.GUIConstants;
import pipe.controllers.application.PipeApplicationController;
import pipe.gui.plugin.GuiModule;
import uk.ac.imperial.pipe.models.petrinet.PetriNet;
import javax.swing.*;
import javax.swing.tree.*;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The ModuleManager class contains methods to create swing components to allow
* the user to load modules and execute methods within them. To use, instantiate
* a ModuleManager object and use the methods to return the required components.
*
* @author Camilla Clifford
* @author David Patterson -- minor changes 24 Nov 2006
* @author Matthew Worthington -- changed the ModuleManger to dynamically load
* all class files in the module directory without the need to update the cfg
* files and provide path properties. Modules can now be dropped into the module
* folder and automatically loaded on all subsequent executions of pipe.
* Also refactored to reduce number of methods loaded with reflection into the
* Jtree which were subsequently never used. Now only loading the run method of
* each of the modules. (Jan,2007)
* @author Pere Bonet - JAR May 2007
*/
public class ModuleManager {
/**
* Load text
*/
private static final String LOAD_NODE_STRING = "Find IModule";
/**
* Class logger
*/
private static final Logger LOGGER = Logger.getLogger(ModuleManager.class.getName());
/**
* PIPE Gui concrete models package
*/
public static final String PIPE_GUI_PLUGIN_CONCRETE_PACKAGE = "pipe.gui.plugin.concrete";
/**
* All modules that have been found in the PIPE_GUI_PLUGIN_CONCRETE_PACKAGE
*/
private final Set<Class<?>> installedModules;
/**
* Main PIPE application controller
*/
private final PipeApplicationController controller;
/**
* Parent of the module loader
*/
private final Component parent;
/**
* Module tree
*/
private JTree moduleTree;
/**
* Tree model
*/
private DefaultTreeModel treeModel;
/**
* Loaded modules
*/
private DefaultMutableTreeNode loadModules;
/**
* Constructor
* @param view view on which the modules should be displayed
* @param controller main PIPE appliaction controller
*/
public ModuleManager(Component view, PipeApplicationController controller) {
this.controller = controller;
parent = view;
installedModules = new HashSet<>();
}
public JTree getModuleTree() {
Collection<Class<? extends GuiModule>> classes = new ArrayList<>();
// get the names of all the classes that are confirmed to be modules
Collection<Class<? extends GuiModule>> names = getModuleClasses();
classes.addAll(names);
// create the root node
DefaultMutableTreeNode root = new DefaultMutableTreeNode("Analysis Module Manager");
// create root children
loadModules = new DefaultMutableTreeNode("Available Modules");
MutableTreeNode add_modules = new DefaultMutableTreeNode(LOAD_NODE_STRING);
// iterate over the class names and create a node for each
for (Class<? extends GuiModule> clazz : classes) {
// create each ModuleClass node using an instantiation of the
// ModuleClass
addClassToTree(clazz);
}
root.add(loadModules);
root.add(add_modules);
treeModel = new DefaultTreeModel(root);
moduleTree = new JTree(treeModel);
moduleTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
moduleTree.addMouseListener(new TreeHandler());
moduleTree.setFocusable(false);
// expand the modules path
moduleTree.expandPath(moduleTree.getPathForRow(1));
return moduleTree;
}
/**
* Finds all the fully qualified (ie: full package names) module classnames
* by recursively searching the rootDirectories
*
* @return
*/
//only load attempt to add .class files
private Collection<Class<? extends GuiModule>> getModuleClasses() {
Collection<Class<? extends GuiModule>> results = new ArrayList<>();
try {
ClassPath classPath = ClassPath.from(this.getClass().getClassLoader());
ImmutableSet<ClassPath.ClassInfo> set = classPath.getTopLevelClasses(PIPE_GUI_PLUGIN_CONCRETE_PACKAGE);
for (ClassPath.ClassInfo classInfo : set) {
Class<?> clazz = classInfo.load();
if (GuiModule.class.isAssignableFrom(clazz)) {
results.add((Class<? extends GuiModule>) clazz);
}
}
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e.getMessage());
}
return results;
}
/**
* Method creates and returns a IModule management tree.
* This consists of two nodes, one resposible for listing all the available
* modules from the module directory, and another for admin options such as
* list refreshing.
* Each node of the tree has it's own user object, for class nodes this will
* be ModuleClass, for method nodes ModuleMethod, and another one yet to be
* implemented for other options.
* When the user clicks on a method node the method is invoked.
* <p/>
* Matthew - modified to reduce unnecessary reflection, now only loading
* the run method of each module class into the tree
*
* @param moduleClass
*/
private void addClassToTree(Class<? extends GuiModule> moduleClass) {
if (installedModules.add(moduleClass)) {
DefaultMutableTreeNode modNode = new DefaultMutableTreeNode(new ModuleClassContainer(moduleClass));
try {
Method tempMethod = moduleClass.getMethod("start", PetriNet.class);
ModuleMethod m = new ModuleMethod(moduleClass, tempMethod);
m.setName(modNode.getUserObject().toString());
modNode.add(new DefaultMutableTreeNode(m));
} catch (SecurityException | NoSuchMethodException e) {
LOGGER.log(Level.SEVERE, e.getMessage());
}
if (modNode.getChildCount() == 1) {
Object m = ((DefaultMutableTreeNode) modNode.getFirstChild()).
getUserObject();
loadModules.add(new DefaultMutableTreeNode(m));
} else {
loadModules.add(modNode);
}
}
}
/**
* Removes a node from the IModule subtree
*
* @param newNode The node to be removed.
*/
private void removeModuleFromTree(MutableTreeNode newNode) {
treeModel.removeNodeFromParent(newNode);
treeModel.reload();
}
/**
* Action object that can be used to remove a module from the ModuleTree
*/
class RemoveModuleAction extends AbstractAction {
private final DefaultMutableTreeNode removeNode;
RemoveModuleAction(TreePath path) {
removeNode = (DefaultMutableTreeNode) path.getLastPathComponent();
}
@Override
public void actionPerformed(ActionEvent e) {
Object o = removeNode.getUserObject();
if (o instanceof ModuleMethod) {
installedModules.remove(((ModuleMethod) o).getModClass());
} else if (o instanceof ModuleClassContainer) {
installedModules.remove(((ModuleClassContainer) o).returnClass());
} else {
LOGGER.log(Level.INFO, "Don't know how to delete class for " + o.getClass());
}
removeModuleFromTree(removeNode);
moduleTree.expandPath(moduleTree.getPathForRow(1));
}
}
// now add in the action listener to enable module method loading.
public class TreeHandler extends MouseAdapter {
@Override
public void mouseClicked(MouseEvent e) {
int selRow = moduleTree.getRowForLocation(e.getX(), e.getY());
TreePath selPath = moduleTree.getPathForLocation(e.getX(), e.getY());
if (selRow != -1) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) selPath.getLastPathComponent();
Object nodeObj = node.getUserObject();
if (e.getClickCount() == 2) {
if (nodeObj instanceof ModuleMethod) {
PetriNet petriNet = controller.getActivePetriNetController().getPetriNet();
((ModuleMethod) nodeObj).execute(petriNet);
} else if (nodeObj.equals(LOAD_NODE_STRING)) {
//Create a file chooser
JFileChooser fc = new JFileChooser();
fc.setFileFilter(new ExtensionFilter(GUIConstants.PROPERTY_FILE_EXTENSION,
GUIConstants.PROPERTY_FILE_DESC)
);
//In response to a button click:
int returnVal = fc.showOpenDialog(parent);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File moduleProp = fc.getSelectedFile();
Class<?> newModuleClass = ModuleLoader.importModule(moduleProp);
if (newModuleClass != null) {
//TODO
// addClassToTree(newModuleClass);
treeModel.reload();
moduleTree.expandPath(moduleTree.getPathForRow(1));
} else {
JOptionPane.showMessageDialog(parent, "Invalid file selected.\n Please ensure the "
+ "class implements the IModule interface and is"
+ " on the CLASSPATH.", "File Selection Error",
JOptionPane.ERROR_MESSAGE
);
}
}
}
}
}
}
/**
* Show the menu popup to run the module
* @param e mouse event
*/
@Override
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) {
showPopupMenu(e);
}
}
/**
* Show the menu popup to run the module
* @param e mouse event
*/
@Override
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) {
showPopupMenu(e);
}
}
/**
* Show the menu for the modules
* @param e mouse event
*/
private void showPopupMenu(MouseEvent e) {
TreePath selPath = moduleTree.getPathForLocation(e.getX(), e.getY());
if (selPath != null) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) selPath.getLastPathComponent();
Object nodeObj = node.getUserObject();
if ((nodeObj instanceof ModuleClassContainer) || (nodeObj instanceof ModuleMethod)) {
JPopupMenu popup = new JPopupMenu();
TreePath removePath = moduleTree.getPathForLocation(e.getX(), e.getY());
JMenuItem menuItem = new JMenuItem(new RemoveModuleAction(removePath));
menuItem.setText("Remove Module");
popup.add(menuItem);
popup.show(e.getComponent(), e.getX(), e.getY());
}
}
}
}
}