|
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; |
|
23 |
| private final boolean _exitOnClose; |
|
24 |
| |
|
25 |
| |
|
26 |
0
| public TreeLogSink(String name) { this(name, false); }
|
|
27 |
| |
|
28 |
| |
|
29 |
| |
|
30 |
| |
|
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 |
| |
|
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 |
| |
|
98 |
| |
|
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 |
| |
|
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 |
| |
|
135 |
| private static class Tree { |
|
136 |
| private volatile long _lastPainted; |
|
137 |
| private volatile JFrame _frame; |
|
138 |
| private final Runnable _onClose; |
|
139 |
| |
|
140 |
| |
|
141 |
| |
|
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);
|
|
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 |
| |
|
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 |
| |
|
167 |
0
| _lastPainted = System.currentTimeMillis();
|
|
168 |
| } |
|
169 |
| }; |
|
170 |
0
| tree.setRootVisible(false);
|
|
171 |
0
| tree.setShowsRootHandles(true);
|
|
172 |
0
| tree.setRowHeight(0);
|
|
173 |
| |
|
174 |
| |
|
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 |
| |
|
226 |
| |
|
227 |
| |
|
228 |
| |
|
229 |
| |
|
230 |
0
| public void checkQueue() {
|
|
231 |
0
| if (System.currentTimeMillis() - _lastPainted > 200) {
|
|
232 |
| |
|
233 |
0
| SwingUtil.attemptClearEventQueue();
|
|
234 |
| } |
|
235 |
| } |
|
236 |
| |
|
237 |
| |
|
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 |
| |
|
248 |
0
| public void push() {
|
|
249 |
0
| _stack.addFirst((DefaultMutableTreeNode) _stack.getFirst().getLastChild());
|
|
250 |
| } |
|
251 |
| |
|
252 |
| |
|
253 |
0
| public void pop() {
|
|
254 |
0
| if (_stack.size() > 1) { _stack.removeFirst(); }
|
|
255 |
| } |
|
256 |
| |
|
257 |
| |
|
258 |
0
| public void dispose() {
|
|
259 |
0
| if (_frame != null) _frame.dispose();
|
|
260 |
0
| _onClose.run();
|
|
261 |
| } |
|
262 |
| |
|
263 |
| } |
|
264 |
| |
|
265 |
| } |