Clover coverage report - PLT Utilities Test Coverage (plt-20120304-r5436)
Coverage timestamp: Sat Mar 3 2012 22:01:56 CST
file stats: LOC: 265   Methods: 37
NCLOC: 200   Classes: 4
 
 Source file Conditionals Statements Methods TOTAL
TreeLogSink.java 0% 2.7% 5.4% 3.1%
coverage coverage
 1    package edu.rice.cs.plt.debug;
 2   
 3    import java.util.Map;
 4    import java.util.HashMap;
 5    import java.util.LinkedList;
 6    import java.awt.*;
 7    import java.io.Serializable;
 8   
 9    import javax.swing.*;
 10    import javax.swing.event.*;
 11    import javax.swing.tree.*;
 12   
 13    import edu.rice.cs.plt.iter.IterUtil;
 14    import edu.rice.cs.plt.iter.SizedIterable;
 15    import edu.rice.cs.plt.lambda.Thunk;
 16    import edu.rice.cs.plt.swing.ShadedTreeCellRenderer;
 17    import edu.rice.cs.plt.swing.SwingUtil;
 18   
 19    public class TreeLogSink extends TextLogSink {
 20   
 21    private final String _name;
 22    private final Map<Long, Tree> _trees; // maps thread IDs to trees
 23    private final boolean _exitOnClose;
 24   
 25    /** Convenience constructor, with {@code exitOnClose} set to {@code false}. */
 26  0 public TreeLogSink(String name) { this(name, false); }
 27   
 28    /**
 29    * @param name Name to use in the window title
 30    * @param exitOnClose Whether {@link System#exit} should be invoked when the last open tree window is closed
 31    */
 32  0 public TreeLogSink(String name, boolean exitOnClose) {
 33  0 _name = name;
 34  0 _trees = new HashMap<Long, Tree>();
 35  0 _exitOnClose = exitOnClose;
 36    }
 37   
 38  0 public void close() {
 39  0 for (Tree t : IterUtil.snapshot(_trees.values())) { t.dispose(); }
 40    }
 41   
 42  0 @Override protected void write(Message m, SizedIterable<String> text) {
 43  0 final Tree tree = getTree(m.thread());
 44  0 final Entry entry = new Entry(m, text);
 45  0 tree.checkQueue();
 46  0 SwingUtilities.invokeLater(new Runnable() {
 47  0 public void run() { tree.addEntry(entry); }
 48    });
 49    }
 50   
 51  0 @Override protected void writeStart(StartMessage m, SizedIterable<String> text) {
 52  0 final Tree tree = getTree(m.thread());
 53  0 final Entry entry = new Entry(m, text);
 54  0 tree.checkQueue();
 55  0 SwingUtilities.invokeLater(new Runnable() {
 56  0 public void run() { tree.addEntry(entry); tree.push(); }
 57    });
 58    }
 59   
 60  0 @Override protected void writeEnd(EndMessage m, SizedIterable<String> text) {
 61  0 final Tree tree = getTree(m.thread());
 62  0 final Entry entry = new Entry(m, text);
 63  0 tree.checkQueue();
 64  0 SwingUtilities.invokeLater(new Runnable() {
 65  0 public void run() { tree.addEntry(entry); tree.pop(); }
 66    });
 67    }
 68   
 69  0 private synchronized Tree getTree(ThreadSnapshot thread) {
 70  0 final Long id = thread.getId();
 71  0 if (!_trees.containsKey(id)) {
 72  0 _trees.put(id, new Tree(_name + ": " + formatThread(thread), new Runnable() {
 73  0 public void run() {
 74  0 _trees.remove(id);
 75  0 if (_exitOnClose && _trees.isEmpty()) { System.exit(0); }
 76    }
 77    }));
 78    }
 79  0 return _trees.get(id);
 80    }
 81   
 82    /** Create a serializable TreeLogSink factory. (For compatibility with {@link RMILogSink}.) */
 83  0 public static Thunk<TreeLogSink> factory(String name) { return factory(name, false); }
 84   
 85  2 public static Thunk<TreeLogSink> factory(String name, boolean exitOnClose) {
 86  2 return new Factory(name, exitOnClose);
 87    }
 88   
 89    private static class Factory implements Thunk<TreeLogSink>, Serializable {
 90    private final String _name;
 91    private final boolean _exitOnClose;
 92  2 public Factory(String name, boolean exitOnClose) { _name = name; _exitOnClose = exitOnClose; }
 93  0 public TreeLogSink value() { return new TreeLogSink(_name, _exitOnClose); }
 94    }
 95   
 96    /**
 97    * An entry in the tree. To minimize its memory footprint, only holds onto user-visible information from
 98    * the given message.
 99    */
 100    private static class Entry {
 101    private final String _time;
 102    private final String _location;
 103    private final SizedIterable<String> _text;
 104    private int _descendents;
 105   
 106    private static final Entry ROOT = new Entry();
 107   
 108  0 public Entry(Message m, SizedIterable<String> text) {
 109  0 _time = formatTime(m.time());
 110  0 _location = formatLocation(m.caller());
 111  0 _text = text;
 112  0 _descendents = 0;
 113    }
 114   
 115    /** Constructor for the dummy root entry. */
 116  0 private Entry() {
 117  0 _time = "<root>";
 118  0 _location = "<root>";
 119  0 _text = IterUtil.empty();
 120  0 _descendents = 0;
 121    }
 122   
 123  0 public int descendents() { return _descendents; }
 124  0 public String time() { return _time; }
 125  0 public String location() { return _location; }
 126  0 public SizedIterable<String> text() { return _text; }
 127  0 public void incrementDescendents() { _descendents++; }
 128  0 public String toString() {
 129  0 return _location + " " + _time;
 130    }
 131   
 132    }
 133   
 134    /** A tree of Entries, presented in a Swing window. */
 135    private static class Tree {
 136    private volatile long _lastPainted;
 137    private volatile JFrame _frame;
 138    private final Runnable _onClose;
 139   
 140    // These fields are synchronized by only accessing them via the event thread
 141    /** Path from current messages' parent to the root of the tree, in bottom-to-top order. */
 142    private final LinkedList<DefaultMutableTreeNode> _stack;
 143    private final DefaultTreeModel _treeModel;
 144   
 145  0 public Tree(final String name, final Runnable onClose) {
 146  0 DefaultMutableTreeNode root = new DefaultMutableTreeNode(Entry.ROOT);
 147  0 _stack = new LinkedList<DefaultMutableTreeNode>();
 148  0 _stack.addFirst(root); // root of the tree
 149  0 _treeModel = new DefaultTreeModel(root);
 150  0 _lastPainted = 0l;
 151  0 _onClose = onClose;
 152  0 _frame = null;
 153  0 SwingUtilities.invokeLater(new Runnable() {
 154  0 public void run() { initGUI(name); }
 155    });
 156    }
 157   
 158    /** Should be invoked from the event thread. */
 159  0 private void initGUI(final String name) {
 160  0 _frame = SwingUtil.makeDisposableFrame(name, 600, 600);
 161  0 SwingUtil.onWindowClosed(_frame, _onClose);
 162   
 163  0 final JTree tree = new JTree(_treeModel) {
 164  0 public void paint(Graphics g) {
 165  0 super.paint(g);
 166    // keep lastPainted up to date
 167  0 _lastPainted = System.currentTimeMillis();
 168    }
 169    };
 170  0 tree.setRootVisible(false);
 171  0 tree.setShowsRootHandles(true);
 172  0 tree.setRowHeight(0);
 173    // Because the root is hidden, we must expand it programmatically
 174    // But we can't until it has a child, so we wait for a child via a listener
 175  0 _treeModel.addTreeModelListener(new TreeModelListener() {
 176  0 public void treeNodesInserted(TreeModelEvent e) {
 177  0 _treeModel.removeTreeModelListener(this);
 178  0 tree.expandPath(e.getTreePath());
 179    }
 180  0 public void treeNodesChanged(TreeModelEvent e) {}
 181  0 public void treeNodesRemoved(TreeModelEvent e) {}
 182  0 public void treeStructureChanged(TreeModelEvent e) {}
 183    });
 184   
 185  0 final JPanel entryCell = SwingUtil.makeVerticalBoxPanel(3, 5);
 186  0 final JPanel top = SwingUtil.makeHorizontalBoxPanel();
 187  0 final JPanel bottom = SwingUtil.makeBorderPanel(3, 15, 0, 0);
 188  0 SwingUtil.setOpaque(false, top, bottom);
 189  0 SwingUtil.setLeftAlignment(top, bottom);
 190  0 SwingUtil.add(entryCell, top, bottom);
 191  0 final JLabel location = new JLabel();
 192  0 final JLabel time = new JLabel();
 193  0 final JLabel descendents = new JLabel();
 194  0 final JTextArea text = new JTextArea();
 195  0 SwingUtil.setMonospacedFont(12, location, time, descendents, text);
 196  0 SwingUtil.setEmptyBorder(0, 10, 0, 0, location, descendents);
 197  0 text.setOpaque(false);
 198  0 SwingUtil.add(top, time, location, descendents);
 199  0 bottom.add(text);
 200  0 tree.setCellRenderer(new TreeCellRenderer() {
 201  0 public Component getTreeCellRendererComponent(JTree tree, Object value,
 202    boolean selected, boolean expanded,
 203    boolean leaf, int row, boolean hasFocus) {
 204  0 Entry e = (Entry) ((DefaultMutableTreeNode) value).getUserObject();
 205  0 if (e.descendents() == 0) { descendents.setVisible(false); }
 206    else {
 207  0 descendents.setVisible(true);
 208  0 descendents.setText("[" + e.descendents() + "]");
 209    }
 210  0 time.setText(e.time());
 211  0 location.setText(e.location());
 212  0 text.setText(IterUtil.multilineToString(e.text()));
 213  0 bottom.setVisible(!text.getText().equals(""));
 214  0 return entryCell;
 215    }
 216    });
 217  0 ShadedTreeCellRenderer.shadeTree(tree, SwingUtil.gray(.1f), SwingUtil.gray(.03f));
 218   
 219  0 _frame.getContentPane().add(new JScrollPane(tree));
 220  0 SwingUtil.displayWindow(_frame);
 221    }
 222   
 223   
 224    /**
 225    * If a sufficient amount of time has passed, clear the event queue. Without this check, expensive
 226    * or frequent logging can lead to all paint events being coalesced (and placed on the *back* of the
 227    * queue) before they have a chance to run. To be effective, this method must <em>not</em> be called
 228    * from the event queue.
 229    */
 230  0 public void checkQueue() {
 231  0 if (System.currentTimeMillis() - _lastPainted > 200) {
 232    // Ensure that paint events are handled periodically.
 233  0 SwingUtil.attemptClearEventQueue();
 234    }
 235    }
 236   
 237    /** Add an entry to the tree. This method must be called from the event queue. */
 238  0 public void addEntry(Entry e) {
 239  0 DefaultMutableTreeNode parent = _stack.getFirst();
 240  0 _treeModel.insertNodeInto(new DefaultMutableTreeNode(e), parent, parent.getChildCount());
 241  0 for (DefaultMutableTreeNode ancestor : _stack) {
 242  0 ((Entry) ancestor.getUserObject()).incrementDescendents();
 243  0 _treeModel.nodeChanged(ancestor);
 244    }
 245    }
 246   
 247    /** Make the last entry a parent for future entries. This method must be called from the event queue. */
 248  0 public void push() {
 249  0 _stack.addFirst((DefaultMutableTreeNode) _stack.getFirst().getLastChild());
 250    }
 251   
 252    /** Add future entries as children of the current grandparent. This method must be called from the event queue. */
 253  0 public void pop() {
 254  0 if (_stack.size() > 1) { _stack.removeFirst(); }
 255    }
 256   
 257    /** Programmatically discard the tree. */
 258  0 public void dispose() {
 259  0 if (_frame != null) _frame.dispose();
 260  0 _onClose.run();
 261    }
 262   
 263    }
 264   
 265    }