gov.sandia.umf.platform.ui.jobs.RunPanel.java Source code

Java tutorial

Introduction

Here is the source code for gov.sandia.umf.platform.ui.jobs.RunPanel.java

Source

/*
Copyright 2013,2016 Sandia Corporation.
Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
the U.S. Government retains certain rights in this software.
Distributed under the BSD-3 license. See the file LICENSE for details.
*/

package gov.sandia.umf.platform.ui.jobs;

import gov.sandia.umf.platform.db.AppData;
import gov.sandia.umf.platform.db.MDir;
import gov.sandia.umf.platform.db.MDoc;
import gov.sandia.umf.platform.db.MNode;
import gov.sandia.umf.platform.execenvs.ExecutionEnv;
import gov.sandia.umf.platform.ui.images.ImageUtil;

import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTree;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.ExpandVetoException;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import org.jfree.chart.ChartPanel;

import replete.util.Lay;

public class RunPanel extends JPanel {
    public NodeBase root;
    public DefaultTreeModel model;
    public JTree tree;
    public JScrollPane treePane;

    public JTextArea displayText;
    public JScrollPane displayPane = new JScrollPane();
    public JButton buttonGraph;
    public JButton buttonRaster;
    public String displayGraph = "";
    public DisplayThread displayThread = null;
    public NodeBase displayNode = null;
    public MDir runs; // Copied from AppData for convenience
    public List<NodeJob> running = new LinkedList<NodeJob>(); // Jobs that we are actively monitoring because they may still be running.

    public RunPanel() {
        root = new NodeBase();
        model = new DefaultTreeModel(root);
        tree = new JTree(model);
        tree.setRootVisible(false);
        tree.setShowsRootHandles(true);
        tree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);

        tree.setCellRenderer(new DefaultTreeCellRenderer() {
            @Override
            public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected,
                    boolean expanded, boolean leaf, int row, boolean hasFocus) {
                super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);

                NodeBase node = (NodeBase) value;
                Icon icon = node.getIcon(expanded); // A node knows whether it should hold other nodes or not, so don't pass leaf to it.
                if (icon == null) {
                    if (leaf)
                        icon = getDefaultLeafIcon();
                    else if (expanded)
                        icon = getDefaultOpenIcon();
                    else
                        icon = getDefaultClosedIcon();
                }
                setIcon(icon);

                return this;
            }
        });

        tree.addTreeSelectionListener(new TreeSelectionListener() {
            public void valueChanged(TreeSelectionEvent e) {
                NodeBase newNode = (NodeBase) tree.getLastSelectedPathComponent();
                if (newNode == null)
                    return;
                if (newNode == displayNode)
                    return;

                if (displayThread != null)
                    synchronized (displayText) {
                        displayThread.stop = true;
                    }
                displayNode = newNode;
                if (displayNode instanceof NodeFile)
                    viewFile();
                else if (displayNode instanceof NodeJob)
                    viewJob();
            }
        });

        tree.addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent e) {
                int keycode = e.getKeyCode();
                if (keycode == KeyEvent.VK_DELETE || keycode == KeyEvent.VK_BACK_SPACE) {
                    delete();
                }
            }
        });

        tree.addTreeWillExpandListener(new TreeWillExpandListener() {
            public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
                TreePath path = event.getPath(); // TODO: can this ever be null?
                Object o = path.getLastPathComponent();
                if (o instanceof NodeJob)
                    ((NodeJob) o).build(tree);
            }

            public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
            }
        });

        tree.addTreeExpansionListener(new TreeExpansionListener() {
            public void treeExpanded(TreeExpansionEvent event) {
                Rectangle node = tree.getPathBounds(event.getPath());
                Rectangle visible = treePane.getViewport().getViewRect();
                visible.height -= node.y - visible.y;
                visible.y = node.y;
                tree.repaint(visible);
            }

            public void treeCollapsed(TreeExpansionEvent event) {
                Rectangle node = tree.getPathBounds(event.getPath());
                Rectangle visible = treePane.getViewport().getViewRect();
                visible.height -= node.y - visible.y;
                visible.y = node.y;
                tree.repaint(visible);
            }
        });

        Thread refreshThread = new Thread() {
            public void run() {
                try {
                    // Initial load
                    synchronized (running) {
                        for (MNode n : AppData.runs)
                            running.add(0, new NodeJob(n)); // This should be efficient on a doubly-linked list.
                        for (NodeJob job : running)
                            root.add(job);
                    }
                    EventQueue.invokeLater(new Runnable() {
                        public void run() {
                            model.nodeStructureChanged(root);
                            if (model.getChildCount(root) > 0)
                                tree.setSelectionRow(0);
                        }
                    });

                    // Periodic refresh to show status of running jobs
                    int shortCycles = 100; // Force full scan on first cycle.
                    while (true) {
                        NodeBase d = displayNode; // Make local copy (atomic action) to prevent it changing from under us
                        if (d instanceof NodeJob)
                            ((NodeJob) d).monitorProgress(RunPanel.this);
                        if (shortCycles++ < 20) {
                            Thread.sleep(1000);
                            continue;
                        }
                        shortCycles = 0;

                        synchronized (running) {
                            Iterator<NodeJob> i = running.iterator();
                            while (i.hasNext()) {
                                NodeJob job = i.next();
                                if (job != d)
                                    job.monitorProgress(RunPanel.this);
                                if (job.complete >= 1)
                                    i.remove();
                            }
                        }
                    }
                } catch (InterruptedException e) {
                }
            }
        };
        refreshThread.setDaemon(true);
        refreshThread.start();

        displayText = new JTextArea();
        displayText.setEditable(false);

        final JCheckBox chkFixedWidth = new JCheckBox("Fixed-Width Font");
        chkFixedWidth.setFocusable(false);
        chkFixedWidth.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                int size = displayText.getFont().getSize();
                if (chkFixedWidth.isSelected()) {
                    displayText.setFont(new Font(Font.MONOSPACED, Font.PLAIN, size));
                } else {
                    displayText.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, size));
                }
            }
        });

        displayPane.setViewportView(displayText);

        ActionListener graphListener = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (displayNode instanceof NodeFile) {
                    NodeFile nf = (NodeFile) displayNode;
                    if (nf.type == NodeFile.Type.Output || nf.type == NodeFile.Type.Result) {
                        String graphType = e.getActionCommand();
                        if (displayPane.getViewport().getView() instanceof ChartPanel
                                && displayGraph.equals(graphType)) {
                            viewFile();
                            displayGraph = "";
                        } else {
                            if (graphType.equals("Graph")) {
                                Plot plot = new Plot(nf.path.getAbsolutePath());
                                if (!plot.columns.isEmpty())
                                    displayPane.setViewportView(plot.createGraphPanel());
                            } else // Raster
                            {
                                Raster raster = new Raster(nf.path.getAbsolutePath(), displayPane.getHeight());
                                displayPane.setViewportView(raster.createGraphPanel());
                            }
                            displayGraph = graphType;
                        }
                    }
                }
            }
        };

        buttonGraph = new JButton("Graph", ImageUtil.getImage("analysis.gif"));
        buttonGraph.setFocusable(false);
        buttonGraph.addActionListener(graphListener);
        buttonGraph.setActionCommand("Graph");

        buttonRaster = new JButton("Raster", ImageUtil.getImage("prnplot.gif"));
        buttonRaster.setFocusable(false);
        buttonRaster.addActionListener(graphListener);
        buttonRaster.setActionCommand("Raster");

        Lay.BLtg(this, "C",
                Lay.SPL(Lay.BL("C", treePane = Lay.sp(tree)), Lay.BL("N",
                        Lay.FL(chkFixedWidth, Lay.FL(buttonGraph, buttonRaster), "hgap=50"), "C", displayPane),
                        "divpixel=250"));
        setFocusCycleRoot(true);
    }

    public class DisplayThread extends Thread {
        public NodeFile node;
        public boolean stop = false;

        public DisplayThread(NodeFile node) {
            super("RunPanel Fetch File");
            this.node = node;
        }

        public void run() {
            try {
                MNode job = ((NodeJob) node.getParent()).source;
                ExecutionEnv env = ExecutionEnv.factory(job.getOrDefault("localhost", "$metadata", "host"));

                // This is the potentially long operation.
                // TODO: What if the file is too big to load & show? Need a viewer that can work with partial segments of a file.
                // TODO: handling of paths for remote files needs work. There are actually two paths: our local dir and the remote dir, and in general they are different.
                final String contents = env.getFileContents(node.path.getAbsolutePath());
                if (stop)
                    return;

                EventQueue.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        synchronized (displayText) {
                            if (stop)
                                return;
                            displayText.setText(contents);
                            displayText.setCaretPosition(0);
                        }
                    }
                });
            } catch (Exception e) {
            }
        };
    }

    public void viewFile() {
        synchronized (displayText) {
            displayText.setText("loading...");
        }
        if (displayPane.getViewport().getView() != displayText)
            displayPane.setViewportView(displayText);

        // Any previous displayThread has already been signaled by our caller
        displayThread = new DisplayThread((NodeFile) displayNode);
        displayThread.start();
    }

    public void viewJob() {
        NodeJob job = (NodeJob) displayNode;
        MNode doc = job.source;

        StringBuilder contents = new StringBuilder();
        contents.append("Status:");
        if (job.complete < 0)
            contents.append(" Waiting");
        else if (job.complete == 0)
            contents.append(" Started");
        else if (job.complete > 0 && job.complete < 1)
            contents.append(" " + Math.round(job.complete * 100) + "%");
        else if (job.complete == 1)
            contents.append(" Success");
        else
            contents.append(" Failed");
        contents.append("\n");
        if (job.dateStarted != null)
            contents.append("  started:  " + job.dateStarted + "\n");
        if (job.dateFinished != null)
            contents.append("  finished: " + job.dateFinished + "\n");
        MNode metadata = doc.child("$metadata");
        if (metadata != null)
            contents.append("\n" + metadata); // TODO: filter out irrelevant items, like "gui.order"

        synchronized (displayText) {
            displayText.setText(contents.toString());
            displayText.setCaretPosition(0);
        }
        if (displayPane.getViewport().getView() != displayText)
            displayPane.setViewportView(displayText);
    }

    public void delete() {
        TreePath[] paths = tree.getSelectionPaths();
        if (paths.length < 1)
            return;

        boolean nextSelectionIsParent = false;
        NodeBase firstSelection = (NodeBase) paths[0].getLastPathComponent();
        NodeBase lastSelection = (NodeBase) paths[paths.length - 1].getLastPathComponent();
        NodeBase nextSelection = (NodeBase) lastSelection.getNextSibling();
        if (nextSelection == null)
            nextSelection = (NodeBase) firstSelection.getPreviousSibling();
        if (nextSelection == null) {
            nextSelection = (NodeBase) firstSelection.getParent();
            nextSelectionIsParent = true;
        }

        for (TreePath path : paths) {
            final NodeBase node = (NodeBase) path.getLastPathComponent();
            model.removeNodeFromParent(node);
            if (displayNode == node) {
                synchronized (displayText) {
                    displayText.setText("");
                }
                if (displayPane.getViewport().getView() != displayText)
                    displayPane.setViewportView(displayText);
            }

            new Thread("RunPanel Delete") {
                public void run() {
                    if (node instanceof NodeJob) {
                        NodeJob job = (NodeJob) node;
                        synchronized (running) {
                            running.remove(job);
                        }

                        MDoc doc = (MDoc) job.source;
                        ExecutionEnv env = ExecutionEnv.factory(doc.getOrDefault("localhost", "$metadata", "host"));
                        String jobName = doc.key();
                        try {
                            env.deleteJob(jobName);
                        } // TODO: Also terminate execution, if possible.
                        catch (Exception e) {
                        }

                        doc.delete();
                    } else if (node instanceof NodeFile) {
                        ((NodeFile) node).path.delete();
                    }
                };
            }.start();
        }

        if (nextSelectionIsParent) {
            if (nextSelection.getChildCount() > 0)
                tree.setSelectionPath(new TreePath(((NodeBase) nextSelection.getChildAt(0)).getPath()));
            else if (nextSelection != root)
                tree.setSelectionPath(new TreePath(nextSelection.getPath()));
        } else {
            tree.setSelectionPath(new TreePath(nextSelection.getPath()));
        }
    }

    public void addNewRun(MNode run) {
        final NodeJob node = new NodeJob(run);
        model.insertNodeInto(node, root, 0); // TODO: race condition on model, between this code and initial load in monitor thread
        if (root.getChildCount() == 1)
            model.nodeStructureChanged(root); // If the list was empty, wee need to give the JTree a little extra kick to get started.
        tree.setSelectionRow(0);
        tree.requestFocusInWindow();

        new Thread("RunPanel Add New Run") {
            public void run() {
                try {
                    // Wait just a little bit, so backend has a chance to deposit a "started" file in the job directory.
                    // Backends should do this as early as possible.
                    // TODO: Add a state to represent ready to run but not yet running. Waiting on queue in a supercomputer would fall in this category.
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                }

                node.monitorProgress(RunPanel.this);
                if (node.complete < 1)
                    synchronized (running) {
                        running.add(0, node);
                    }
            };
        }.start();
    }
}