1 /* 2 * Copyright (c) 1998, 2011, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /* 27 * This source code is provided to illustrate the usage of a given feature 28 * or technique and has been deliberately simplified. Additional steps 29 * required for a production-quality application, such as security checks, 30 * input validation and proper error handling, might not be present in 31 * this sample code. 32 */ 33 34 35 package com.sun.tools.example.debug.gui; 36 37 import java.util.*; 38 import java.util.List; // Must import explicitly due to conflict with javax.awt.List 39 40 import javax.swing.*; 41 import javax.swing.tree.*; 42 import java.awt.*; 43 import java.awt.event.*; 44 45 import com.sun.jdi.*; 46 import com.sun.tools.example.debug.event.*; 47 import com.sun.tools.example.debug.bdi.*; 48 49 //### Bug: If the name of a thread is changed via Thread.setName(), the 50 //### thread tree view does not reflect this. The name of the thread at 51 //### the time it is created is used throughout its lifetime. 52 53 public class ThreadTreeTool extends JPanel { 54 55 private static final long serialVersionUID = 4168599992853038878L; 56 57 private Environment env; 58 59 private ExecutionManager runtime; 60 private SourceManager sourceManager; 61 private ClassManager classManager; 62 63 private JTree tree; 64 private DefaultTreeModel treeModel; 65 private ThreadTreeNode root; 66 private SearchPath sourcePath; 67 68 private CommandInterpreter interpreter; 69 70 private static String HEADING = "THREADS"; 71 72 public ThreadTreeTool(Environment env) { 73 74 super(new BorderLayout()); 75 76 this.env = env; 77 this.runtime = env.getExecutionManager(); 78 this.sourceManager = env.getSourceManager(); 79 80 this.interpreter = new CommandInterpreter(env); 81 82 root = createThreadTree(HEADING); 83 treeModel = new DefaultTreeModel(root); 84 85 // Create a tree that allows one selection at a time. 86 87 tree = new JTree(treeModel); 88 tree.setSelectionModel(new SingleLeafTreeSelectionModel()); 89 90 MouseListener ml = new MouseAdapter() { 91 @Override 92 public void mouseClicked(MouseEvent e) { 93 int selRow = tree.getRowForLocation(e.getX(), e.getY()); 94 TreePath selPath = tree.getPathForLocation(e.getX(), e.getY()); 95 if(selRow != -1) { 96 if(e.getClickCount() == 1) { 97 ThreadTreeNode node = 98 (ThreadTreeNode)selPath.getLastPathComponent(); 99 // If user clicks on leaf, select it, and issue 'thread' command. 100 if (node.isLeaf()) { 101 tree.setSelectionPath(selPath); 102 interpreter.executeCommand("thread " + 103 node.getThreadId() + 104 " (\"" + 105 node.getName() + "\")"); 106 } 107 } 108 } 109 } 110 }; 111 112 tree.addMouseListener(ml); 113 114 JScrollPane treeView = new JScrollPane(tree); 115 add(treeView); 116 117 // Create listener. 118 ThreadTreeToolListener listener = new ThreadTreeToolListener(); 119 runtime.addJDIListener(listener); 120 runtime.addSessionListener(listener); 121 122 //### remove listeners on exit! 123 } 124 125 HashMap<ThreadReference, List<String>> threadTable = new HashMap<ThreadReference, List<String>>(); 126 127 private List<String> threadPath(ThreadReference thread) { 128 // May exit abnormally if VM disconnects. 129 List<String> l = new ArrayList<String>(); 130 l.add(0, thread.name()); 131 ThreadGroupReference group = thread.threadGroup(); 132 while (group != null) { 133 l.add(0, group.name()); 134 group = group.parent(); 135 } 136 return l; 137 } 138 139 private class ThreadTreeToolListener extends JDIAdapter 140 implements JDIListener, SessionListener { 141 142 // SessionListener 143 144 @Override 145 public void sessionStart(EventObject e) { 146 try { 147 for (ThreadReference thread : runtime.allThreads()) { 148 root.addThread(thread); 149 } 150 } catch (VMDisconnectedException ee) { 151 // VM went away unexpectedly. 152 } catch (NoSessionException ee) { 153 // Ignore. Should not happen. 154 } 155 } 156 157 @Override 158 public void sessionInterrupt(EventObject e) {} 159 @Override 160 public void sessionContinue(EventObject e) {} 161 162 163 // JDIListener 164 165 @Override 166 public void threadStart(ThreadStartEventSet e) { 167 root.addThread(e.getThread()); 168 } 169 170 @Override 171 public void threadDeath(ThreadDeathEventSet e) { 172 root.removeThread(e.getThread()); 173 } 174 175 @Override 176 public void vmDisconnect(VMDisconnectEventSet e) { 177 // Clear the contents of this view. 178 root = createThreadTree(HEADING); 179 treeModel = new DefaultTreeModel(root); 180 tree.setModel(treeModel); 181 threadTable = new HashMap<ThreadReference, List<String>>(); 182 } 183 184 } 185 186 ThreadTreeNode createThreadTree(String label) { 187 return new ThreadTreeNode(label, null); 188 } 189 190 class ThreadTreeNode extends DefaultMutableTreeNode { 191 192 String name; 193 ThreadReference thread; // null if thread group 194 long uid; 195 String description; 196 197 ThreadTreeNode(String name, ThreadReference thread) { 198 if (name == null) { 199 name = "<unnamed>"; 200 } 201 this.name = name; 202 this.thread = thread; 203 if (thread == null) { 204 this.uid = -1; 205 this.description = name; 206 } else { 207 this.uid = thread.uniqueID(); 208 this.description = name + " (t@" + Long.toHexString(uid) + ")"; 209 } 210 } 211 212 @Override 213 public String toString() { 214 return description; 215 } 216 217 public String getName() { 218 return name; 219 } 220 221 public ThreadReference getThread() { 222 return thread; 223 } 224 225 public String getThreadId() { 226 return "t@" + Long.toHexString(uid); 227 } 228 229 private boolean isThreadGroup() { 230 return (thread == null); 231 } 232 233 @Override 234 public boolean isLeaf() { 235 return !isThreadGroup(); 236 } 237 238 public void addThread(ThreadReference thread) { 239 // This can fail if the VM disconnects. 240 // It is important to do all necessary JDI calls 241 // before modifying the tree, so we don't abort 242 // midway through! 243 if (threadTable.get(thread) == null) { 244 // Add thread only if not already present. 245 try { 246 List<String> path = threadPath(thread); 247 // May not get here due to exception. 248 // If we get here, we are committed. 249 // We must not leave the tree partially updated. 250 try { 251 threadTable.put(thread, path); 252 addThread(path, thread); 253 } catch (Throwable tt) { 254 //### Assertion failure. 255 throw new RuntimeException("ThreadTree corrupted"); 256 } 257 } catch (VMDisconnectedException ee) { 258 // Ignore. Thread will not be added. 259 } 260 } 261 } 262 263 private void addThread(List<String> threadPath, ThreadReference thread) { 264 int size = threadPath.size(); 265 if (size == 0) { 266 return; 267 } else if (size == 1) { 268 String name = threadPath.get(0); 269 insertNode(name, thread); 270 } else { 271 String head = threadPath.get(0); 272 List<String> tail = threadPath.subList(1, size); 273 ThreadTreeNode child = insertNode(head, null); 274 child.addThread(tail, thread); 275 } 276 } 277 278 private ThreadTreeNode insertNode(String name, ThreadReference thread) { 279 for (int i = 0; i < getChildCount(); i++) { 280 ThreadTreeNode child = (ThreadTreeNode)getChildAt(i); 281 int cmp = name.compareTo(child.getName()); 282 if (cmp == 0 && thread == null) { 283 // A like-named interior node already exists. 284 return child; 285 } else if (cmp < 0) { 286 // Insert new node before the child. 287 ThreadTreeNode newChild = new ThreadTreeNode(name, thread); 288 treeModel.insertNodeInto(newChild, this, i); 289 return newChild; 290 } 291 } 292 // Insert new node after last child. 293 ThreadTreeNode newChild = new ThreadTreeNode(name, thread); 294 treeModel.insertNodeInto(newChild, this, getChildCount()); 295 return newChild; 296 } 297 298 public void removeThread(ThreadReference thread) { 299 List<String> threadPath = threadTable.get(thread); 300 // Only remove thread if we recorded it in table. 301 // Original add may have failed due to VM disconnect. 302 if (threadPath != null) { 303 removeThread(threadPath, thread); 304 } 305 } 306 307 private void removeThread(List<String> threadPath, ThreadReference thread) { 308 int size = threadPath.size(); 309 if (size == 0) { 310 return; 311 } else if (size == 1) { 312 String name = threadPath.get(0); 313 ThreadTreeNode child = findLeafNode(thread, name); 314 treeModel.removeNodeFromParent(child); 315 } else { 316 String head = threadPath.get(0); 317 List<String> tail = threadPath.subList(1, size); 318 ThreadTreeNode child = findInternalNode(head); 319 child.removeThread(tail, thread); 320 if (child.isThreadGroup() && child.getChildCount() < 1) { 321 // Prune non-leaf nodes with no children. 322 treeModel.removeNodeFromParent(child); 323 } 324 } 325 } 326 327 private ThreadTreeNode findLeafNode(ThreadReference thread, String name) { 328 for (int i = 0; i < getChildCount(); i++) { 329 ThreadTreeNode child = (ThreadTreeNode)getChildAt(i); 330 if (child.getThread() == thread) { 331 if (!name.equals(child.getName())) { 332 //### Assertion failure. 333 throw new RuntimeException("name mismatch"); 334 } 335 return child; 336 } 337 } 338 //### Assertion failure. 339 throw new RuntimeException("not found"); 340 } 341 342 private ThreadTreeNode findInternalNode(String name) { 343 for (int i = 0; i < getChildCount(); i++) { 344 ThreadTreeNode child = (ThreadTreeNode)getChildAt(i); 345 if (name.equals(child.getName())) { 346 return child; 347 } 348 } 349 //### Assertion failure. 350 throw new RuntimeException("not found"); 351 } 352 353 } 354 355 }