package jsetool.gui; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.*; import java.util.Collection; import java.util.Iterator; import javax.swing.*; import javax.swing.border.BevelBorder; import jsetool.data.ExplorationState; import jsetool.data.Transition; import jsetool.engine.StateExplorer; /** * @author Mike Lawson * * @since Nov 7, 2004 * * The JSEToolMainPane class is the primary user interface for the tool. It * consists of two areas to edit Finite State Machines and a results area. * * */ public class JSEToolMainPane extends JPanel { protected JPanel machine1; protected FSMEditingPane machine1EditingPane; protected JPanel machine2; protected FSMEditingPane machine2EditingPane; protected JSplitPane mainSplit; protected JPanel resultsPanel; protected ReachabilityResults lastResults; /** * Constructs a new, empty, pane. */ public JSEToolMainPane() { buildDisplay(); } /** * Sets the results object and updates the display. * * @param results */ public void setResults(ReachabilityResults results) { lastResults = results; setResultsPane(generateResultsPane()); } /** * Generates a results pane with both the exploration state diagram and the * list of unused states and transitions per machine. * * @return Component */ protected Component generateResultsPane() { JTabbedPane tabbedPane = new JTabbedPane(); tabbedPane.addTab("Diagram", lastResults.getResultsPane()); tabbedPane.addTab("Summary", generateSummaryPane()); tabbedPane.addTab("M1 Unused", generateUnusedPane("M1", lastResults.getM1UnusedStates(), lastResults.getM1UnusedTransitions())); tabbedPane.addTab("M2 Unused", generateUnusedPane("M2", lastResults.getM2UnusedStates(), lastResults.getM2UnusedTransitions())); return tabbedPane; } /** * Generates a pane that displays summary information about the execution. * * @return Component */ protected Component generateSummaryPane() { JPanel summaryPane = new JPanel(); summaryPane.setLayout(new BorderLayout()); JEditorPane editor = new JEditorPane(); editor.setContentType("text/html"); // Add title String displayHTML = "Summary of Execution"; int numberOfUnusedStates = lastResults.getM1UnusedStates().size() + lastResults.getM2UnusedStates().size(); int numberOfUnusedTransitions = lastResults.getM1UnusedTransitions().size() + lastResults.getM2UnusedTransitions().size(); // Add list of summary items displayHTML += ""; editor.setText(displayHTML); editor.setEditable(false); summaryPane.add(editor, BorderLayout.CENTER); return summaryPane; } /** * Generates the pane that display what is unused in the given machine. * * @param machineName * @param unusedStates * @param unusedTransitions * @return Component */ protected Component generateUnusedPane(String machineName, Collection unusedStates, Collection unusedTransitions) { JPanel unusedPane = new JPanel(); unusedPane.setLayout(new BorderLayout()); JEditorPane editor = new JEditorPane(); editor.setContentType("text/html"); // Add title of unused states String displayHTML = "Unused States for Machine: " + machineName + ""; // Add list of states displayHTML += ""; // Add title of unused states displayHTML += "
Unused Transitions for Machine: " + machineName + ""; displayHTML += "
"; editor.setText(displayHTML); editor.setEditable(false); unusedPane.add(editor, BorderLayout.CENTER); return unusedPane; } /** * Replaces the current results area with a new one. * * @param p * the ExplorationStateDrawingPane that has the new results. */ protected void setResultsPane(Component p) { Component results = new JScrollPane(p); try { resultsPanel.remove(1); } catch (RuntimeException e) { // nada - just means there's nothing yet in the panel. } resultsPanel.add(results, BorderLayout.CENTER, 1); mainSplit.revalidate(); } /** * Adds the results area to the display. */ protected void addResultsArea() { resultsPanel = new JPanel(); resultsPanel.setLayout(new BorderLayout()); resultsPanel.setPreferredSize(new Dimension(350, 350)); resultsPanel.add(createButtonPanelForResultsArea(), BorderLayout.NORTH, 0); setResultsEmpty(); mainSplit.add(resultsPanel, JSplitPane.BOTTOM); } /** * */ private void setResultsEmpty() { JPanel empty = new JPanel(); setResultsPane(empty); } /** * Builds the display. */ protected void buildDisplay() { setLayout(new BorderLayout()); mainSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT); mainSplit.setOneTouchExpandable(true); addFSMEditingPanes(); addResultsArea(); setPreferredSize(new Dimension(850, 550)); add(mainSplit, BorderLayout.CENTER); } /** * Performs the reachability analysis for the data drawn on the screen. * * @return ReachabilityResults */ protected ReachabilityResults performReachabilityAnalysis() { // Show error if one or both machines do not have an initial state. LabeledState m1Initial = machine1EditingPane.getInitialState(); LabeledState m2Initial = machine2EditingPane.getInitialState(); if ((m1Initial == null) || (m2Initial == null)) { JOptionPane.showMessageDialog(this, "Cannot perform analysis, both machines must have an initial state.", "JSETool Error", JOptionPane.ERROR_MESSAGE); return null; } DrawingToEngineDataAdapter m1Adapter = new DrawingToEngineDataAdapter(); m1Adapter.setInitial(m1Initial); m1Adapter.exploreAllStates(); DrawingToEngineDataAdapter m2Adapter = new DrawingToEngineDataAdapter(); m2Adapter.setInitial(m2Initial); m2Adapter.exploreAllStates(); StateExplorer se = new StateExplorer(m1Adapter.getInitialState(), m2Adapter.getInitialState()); ExplorationState exploredState = se.exploreStates(); ExplorationStateLeveler leveler = new ExplorationStateLeveler(exploredState); ExplorationStateDrawingPane ldp = new ExplorationStateDrawingPane(leveler); ReachabilityResults results = new ReachabilityResults(); results.setResultsPane(ldp); results.setMillsToCompute(se.getMillisToCompute()); results.setM1UnusedStates(m1Adapter.getUnusedStates()); results.setM2UnusedStates(m2Adapter.getUnusedStates()); results.setM1UnusedTransitions(m1Adapter.getUnusedTransitions()); results.setM2UnusedTransitions(m2Adapter.getUnusedTransitions()); return results; } /** * */ protected void resetStatesOfExploredObjects() { machine1EditingPane.resetObjects(); machine2EditingPane.resetObjects(); } /** * Adds the panes that allow the user to edit FSMs */ private void addFSMEditingPanes() { machine1EditingPane = new FSMEditingPane(); machine2EditingPane = new FSMEditingPane(); machine1 = new JPanel(new BorderLayout()); JPanel m1Title = new JPanel(); m1Title.setLayout(new FlowLayout()); m1Title.add(new JLabel("M1")); m1Title.setBorder(new BevelBorder(0)); machine1.add(m1Title, BorderLayout.NORTH); machine1.add(machine1EditingPane, BorderLayout.CENTER); machine2 = new JPanel(new BorderLayout()); JPanel m2Title = new JPanel(); m2Title.setLayout(new FlowLayout()); m2Title.add(new JLabel("M2")); m2Title.setBorder(new BevelBorder(0)); machine2.add(m2Title, BorderLayout.NORTH); machine2.add(machine2EditingPane, BorderLayout.CENTER); JSplitPane machineSplits = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); machineSplits.setOneTouchExpandable(true); machineSplits.add(machine1, 0); machineSplits.add(machine2, 0); machineSplits.setAutoscrolls(true); machineSplits.setDividerLocation(0.50D); JPanel top = new JPanel(); top.setLayout(new BorderLayout()); top.add(machineSplits, BorderLayout.CENTER); top.add(createButtonPanelForStateMachines(), BorderLayout.SOUTH); mainSplit.add(top, JSplitPane.TOP); mainSplit.setDividerLocation(250); } /** * Called when the calculate button is pushed. * */ private void calculateButtonPushed() { ReachabilityResults results = performReachabilityAnalysis(); if (results == null) return; resetStatesOfExploredObjects(); setResults(results); } /** * Creates the button panel for the results area. * * @return Component * */ private Component createButtonPanelForResultsArea() { JPanel resultsButtonPanel = new JPanel(); resultsButtonPanel.setLayout(new FlowLayout()); JButton saveResultsButton = makeButton("images/save.gif", "Save"); saveResultsButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { saveButtonPushed(); } }); resultsButtonPanel.add(saveResultsButton); JButton loadResultsButton = makeButton("images/open.gif", "Load"); loadResultsButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { loadButtonPushed(); } }); resultsButtonPanel.add(loadResultsButton); JButton clearButton = makeButton("images/new.gif", "Clear"); clearButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { clearDiagram(); } }); resultsButtonPanel.add(clearButton); return resultsButtonPanel; } private void saveButtonPushed() { try { JFileChooser fc = new JFileChooser(); int retval = fc.showSaveDialog(this); if (retval == JFileChooser.APPROVE_OPTION) { saveDrawingPane(fc.getSelectedFile()); } } catch (Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(this, "Failed to save drawing", "Error Saving", JOptionPane.ERROR_MESSAGE); } } /** * Saves the drawing pane to the given file name. * * @param file * @throws Exception */ public void saveDrawingPane(File file) throws Exception { File saveFile = file; FileOutputStream fos = new FileOutputStream(saveFile); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(lastResults); } private void clearDiagram() { setResultsEmpty(); } private void loadButtonPushed() { try { JFileChooser fc = new JFileChooser(); int retval = fc.showOpenDialog(this); if (retval == JFileChooser.APPROVE_OPTION) { readDrawingPane(fc.getSelectedFile()); } } catch (Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(this, "Failed to load drawing", "Error Reading", JOptionPane.ERROR_MESSAGE); } } /** * Reads the drawing pane from the given file name. * * @param file * @throws Exception */ public void readDrawingPane(File file) throws Exception { File saveFile = file; FileInputStream fis = new FileInputStream(saveFile); ObjectInputStream ois = new ObjectInputStream(fis); ReachabilityResults loadedResults = (ReachabilityResults) ois.readObject(); setResults(loadedResults); } /** * Builds the panel of buttons that sits between the editing panes and the * results pane. * * @return Component */ private Component createButtonPanelForStateMachines() { JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new FlowLayout()); JButton calculateButton = new JButton("Perform RA"); calculateButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { calculateButtonPushed(); } }); buttonPanel.add(calculateButton); return buttonPanel; } /** * @return */ private JButton makeButton(String imagePath, String defaultText) { ImageIcon icon = getImageIcon(imagePath); JButton button = null; if (icon == null) { button = new JButton(defaultText); } else { Image img = icon.getImage(); icon.setImage(img.getScaledInstance(12, 12, Image.SCALE_SMOOTH)); button = new JButton(icon); button.setPreferredSize(new Dimension(24, 24)); button.setToolTipText(defaultText); } return button; } private ImageIcon getImageIcon(String path) { int MAX_IMAGE_SIZE = 2400; //Change this to the size of //your biggest image, in bytes. int count = 0; BufferedInputStream imgStream = new BufferedInputStream( this.getClass().getResourceAsStream(path)); if (imgStream != null) { byte buf[] = new byte[MAX_IMAGE_SIZE]; try { count = imgStream.read(buf); imgStream.close(); } catch (java.io.IOException ioe) { System.err.println("Couldn't read stream from file: " + path); ioe.printStackTrace(); return null; } if (count <= 0) { System.err.println("Empty file: " + path); return null; } return new ImageIcon(Toolkit.getDefaultToolkit().createImage(buf)); } else { System.err.println("Couldn't find file: " + path); return null; } } }