Showing the changes to java/pkg/com/sun/tools/example/debug/gui/ThreadTreeTool.java (1 of 18352 files). Show all
Unified · Split
| @@ -0,0 +1,346 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1998, 2008, 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 | package com.sun.tools.example.debug.gui; | ||
| 27 | |||
| 28 | import java.util.*; | ||
| 29 | import java.util.List; // Must import explicitly due to conflict with javax.awt.List | ||
| 30 | |||
| 31 | import javax.swing.*; | ||
| 32 | import javax.swing.tree.*; | ||
| 33 | import java.awt.*; | ||
| 34 | import java.awt.event.*; | ||
| 35 | |||
| 36 | import com.sun.jdi.*; | ||
| 37 | import com.sun.tools.example.debug.event.*; | ||
| 38 | import com.sun.tools.example.debug.bdi.*; | ||
| 39 | |||
| 40 | //### Bug: If the name of a thread is changed via Thread.setName(), the | ||
| 41 | //### thread tree view does not reflect this. The name of the thread at | ||
| 42 | //### the time it is created is used throughout its lifetime. | ||
| 43 | |||
| 44 | public class ThreadTreeTool extends JPanel { | ||
| 45 | |||
| 46 | private static final long serialVersionUID = 4168599992853038878L; | ||
| 47 | |||
| 48 | private Environment env; | ||
| 49 | |||
| 50 | private ExecutionManager runtime; | ||
| 51 | private SourceManager sourceManager; | ||
| 52 | private ClassManager classManager; | ||
| 53 | |||
| 54 | private JTree tree; | ||
| 55 | private DefaultTreeModel treeModel; | ||
| 56 | private ThreadTreeNode root; | ||
| 57 | private SearchPath sourcePath; | ||
| 58 | |||
| 59 | private CommandInterpreter interpreter; | ||
| 60 | |||
| 61 | private static String HEADING = "THREADS"; | ||
| 62 | |||
| 63 | public ThreadTreeTool(Environment env) { | ||
| 64 | |||
| 65 | super(new BorderLayout()); | ||
| 66 | |||
| 67 | this.env = env; | ||
| 68 | this.runtime = env.getExecutionManager(); | ||
| 69 | this.sourceManager = env.getSourceManager(); | ||
| 70 | |||
| 71 | this.interpreter = new CommandInterpreter(env); | ||
| 72 | |||
| 73 | root = createThreadTree(HEADING); | ||
| 74 | treeModel = new DefaultTreeModel(root); | ||
| 75 | |||
| 76 | // Create a tree that allows one selection at a time. | ||
| 77 | |||
| 78 | tree = new JTree(treeModel); | ||
| 79 | tree.setSelectionModel(new SingleLeafTreeSelectionModel()); | ||
| 80 | |||
| 81 | MouseListener ml = new MouseAdapter() { | ||
| 82 | @Override | ||
| 83 | public void mouseClicked(MouseEvent e) { | ||
| 84 | int selRow = tree.getRowForLocation(e.getX(), e.getY()); | ||
| 85 | TreePath selPath = tree.getPathForLocation(e.getX(), e.getY()); | ||
| 86 | if(selRow != -1) { | ||
| 87 | if(e.getClickCount() == 1) { | ||
| 88 | ThreadTreeNode node = | ||
| 89 | (ThreadTreeNode)selPath.getLastPathComponent(); | ||
| 90 | // If user clicks on leaf, select it, and issue 'thread' command. | ||
| 91 | if (node.isLeaf()) { | ||
| 92 | tree.setSelectionPath(selPath); | ||
| 93 | interpreter.executeCommand("thread " + | ||
| 94 | node.getThreadId() + | ||
| 95 | " (\"" + | ||
| 96 | node.getName() + "\")"); | ||
| 97 | } | ||
| 98 | } | ||
| 99 | } | ||
| 100 | } | ||
| 101 | }; | ||
| 102 | |||
| 103 | tree.addMouseListener(ml); | ||
| 104 | |||
| 105 | JScrollPane treeView = new JScrollPane(tree); | ||
| 106 | add(treeView); | ||
| 107 | |||
| 108 | // Create listener. | ||
| 109 | ThreadTreeToolListener listener = new ThreadTreeToolListener(); | ||
| 110 | runtime.addJDIListener(listener); | ||
| 111 | runtime.addSessionListener(listener); | ||
| 112 | |||
| 113 | //### remove listeners on exit! | ||
| 114 | } | ||
| 115 | |||
| 116 | HashMap<ThreadReference, List<String>> threadTable = new HashMap<ThreadReference, List<String>>(); | ||
| 117 | |||
| 118 | private List<String> threadPath(ThreadReference thread) { | ||
| 119 | // May exit abnormally if VM disconnects. | ||
| 120 | List<String> l = new ArrayList<String>(); | ||
| 121 | l.add(0, thread.name()); | ||
| 122 | ThreadGroupReference group = thread.threadGroup(); | ||
| 123 | while (group != null) { | ||
| 124 | l.add(0, group.name()); | ||
| 125 | group = group.parent(); | ||
| 126 | } | ||
| 127 | return l; | ||
| 128 | } | ||
| 129 | |||
| 130 | private class ThreadTreeToolListener extends JDIAdapter | ||
| 131 | implements JDIListener, SessionListener { | ||
| 132 | |||
| 133 | // SessionListener | ||
| 134 | |||
| 135 | @Override | ||
| 136 | public void sessionStart(EventObject e) { | ||
| 137 | try { | ||
| 138 | for (ThreadReference thread : runtime.allThreads()) { | ||
| 139 | root.addThread(thread); | ||
| 140 | } | ||
| 141 | } catch (VMDisconnectedException ee) { | ||
| 142 | // VM went away unexpectedly. | ||
| 143 | } catch (NoSessionException ee) { | ||
| 144 | // Ignore. Should not happen. | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | @Override | ||
| 149 | public void sessionInterrupt(EventObject e) {} | ||
| 150 | @Override | ||
| 151 | public void sessionContinue(EventObject e) {} | ||
| 152 | |||
| 153 | |||
| 154 | // JDIListener | ||
| 155 | |||
| 156 | @Override | ||
| 157 | public void threadStart(ThreadStartEventSet e) { | ||
| 158 | root.addThread(e.getThread()); | ||
| 159 | } | ||
| 160 | |||
| 161 | @Override | ||
| 162 | public void threadDeath(ThreadDeathEventSet e) { | ||
| 163 | root.removeThread(e.getThread()); | ||
| 164 | } | ||
| 165 | |||
| 166 | @Override | ||
| 167 | public void vmDisconnect(VMDisconnectEventSet e) { | ||
| 168 | // Clear the contents of this view. | ||
| 169 | root = createThreadTree(HEADING); | ||
| 170 | treeModel = new DefaultTreeModel(root); | ||
| 171 | tree.setModel(treeModel); | ||
| 172 | threadTable = new HashMap<ThreadReference, List<String>>(); | ||
| 173 | } | ||
| 174 | |||
| 175 | } | ||
| 176 | |||
| 177 | ThreadTreeNode createThreadTree(String label) { | ||
| 178 | return new ThreadTreeNode(label, null); | ||
| 179 | } | ||
| 180 | |||
| 181 | class ThreadTreeNode extends DefaultMutableTreeNode { | ||
| 182 | |||
| 183 | String name; | ||
| 184 | ThreadReference thread; // null if thread group | ||
| 185 | long uid; | ||
| 186 | String description; | ||
| 187 | |||
| 188 | ThreadTreeNode(String name, ThreadReference thread) { | ||
| 189 | if (name == null) { | ||
| 190 | name = "<unnamed>"; | ||
| 191 | } | ||
| 192 | this.name = name; | ||
| 193 | this.thread = thread; | ||
| 194 | if (thread == null) { | ||
| 195 | this.uid = -1; | ||
| 196 | this.description = name; | ||
| 197 | } else { | ||
| 198 | this.uid = thread.uniqueID(); | ||
| 199 | this.description = name + " (t@" + Long.toHexString(uid) + ")"; | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | @Override | ||
| 204 | public String toString() { | ||
| 205 | return description; | ||
| 206 | } | ||
| 207 | |||
| 208 | public String getName() { | ||
| 209 | return name; | ||
| 210 | } | ||
| 211 | |||
| 212 | public ThreadReference getThread() { | ||
| 213 | return thread; | ||
| 214 | } | ||
| 215 | |||
| 216 | public String getThreadId() { | ||
| 217 | return "t@" + Long.toHexString(uid); | ||
| 218 | } | ||
| 219 | |||
| 220 | private boolean isThreadGroup() { | ||
| 221 | return (thread == null); | ||
| 222 | } | ||
| 223 | |||
| 224 | @Override | ||
| 225 | public boolean isLeaf() { | ||
| 226 | return !isThreadGroup(); | ||
| 227 | } | ||
| 228 | |||
| 229 | public void addThread(ThreadReference thread) { | ||
| 230 | // This can fail if the VM disconnects. | ||
| 231 | // It is important to do all necessary JDI calls | ||
| 232 | // before modifying the tree, so we don't abort | ||
| 233 | // midway through! | ||
| 234 | if (threadTable.get(thread) == null) { | ||
| 235 | // Add thread only if not already present. | ||
| 236 | try { | ||
| 237 | List<String> path = threadPath(thread); | ||
| 238 | // May not get here due to exception. | ||
| 239 | // If we get here, we are committed. | ||
| 240 | // We must not leave the tree partially updated. | ||
| 241 | try { | ||
| 242 | threadTable.put(thread, path); | ||
| 243 | addThread(path, thread); | ||
| 244 | } catch (Throwable tt) { | ||
| 245 | //### Assertion failure. | ||
| 246 | throw new RuntimeException("ThreadTree corrupted"); | ||
| 247 | } | ||
| 248 | } catch (VMDisconnectedException ee) { | ||
| 249 | // Ignore. Thread will not be added. | ||
| 250 | } | ||
| 251 | } | ||
| 252 | } | ||
| 253 | |||
| 254 | private void addThread(List<String> threadPath, ThreadReference thread) { | ||
| 255 | int size = threadPath.size(); | ||
| 256 | if (size == 0) { | ||
| 257 | return; | ||
| 258 | } else if (size == 1) { | ||
| 259 | String name = threadPath.get(0); | ||
| 260 | insertNode(name, thread); | ||
| 261 | } else { | ||
| 262 | String head = threadPath.get(0); | ||
| 263 | List<String> tail = threadPath.subList(1, size); | ||
| 264 | ThreadTreeNode child = insertNode(head, null); | ||
| 265 | child.addThread(tail, thread); | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | private ThreadTreeNode insertNode(String name, ThreadReference thread) { | ||
| 270 | for (int i = 0; i < getChildCount(); i++) { | ||
| 271 | ThreadTreeNode child = (ThreadTreeNode)getChildAt(i); | ||
| 272 | int cmp = name.compareTo(child.getName()); | ||
| 273 | if (cmp == 0 && thread == null) { | ||
| 274 | // A like-named interior node already exists. | ||
| 275 | return child; | ||
| 276 | } else if (cmp < 0) { | ||
| 277 | // Insert new node before the child. | ||
| 278 | ThreadTreeNode newChild = new ThreadTreeNode(name, thread); | ||
| 279 | treeModel.insertNodeInto(newChild, this, i); | ||
| 280 | return newChild; | ||
| 281 | } | ||
| 282 | } | ||
| 283 | // Insert new node after last child. | ||
| 284 | ThreadTreeNode newChild = new ThreadTreeNode(name, thread); | ||
| 285 | treeModel.insertNodeInto(newChild, this, getChildCount()); | ||
| 286 | return newChild; | ||
| 287 | } | ||
| 288 | |||
| 289 | public void removeThread(ThreadReference thread) { | ||
| 290 | List<String> threadPath = threadTable.get(thread); | ||
| 291 | // Only remove thread if we recorded it in table. | ||
| 292 | // Original add may have failed due to VM disconnect. | ||
| 293 | if (threadPath != null) { | ||
| 294 | removeThread(threadPath, thread); | ||
| 295 | } | ||
| 296 | } | ||
| 297 | |||
| 298 | private void removeThread(List<String> threadPath, ThreadReference thread) { | ||
| 299 | int size = threadPath.size(); | ||
| 300 | if (size == 0) { | ||
| 301 | return; | ||
| 302 | } else if (size == 1) { | ||
| 303 | String name = threadPath.get(0); | ||
| 304 | ThreadTreeNode child = findLeafNode(thread, name); | ||
| 305 | treeModel.removeNodeFromParent(child); | ||
| 306 | } else { | ||
| 307 | String head = threadPath.get(0); | ||
| 308 | List<String> tail = threadPath.subList(1, size); | ||
| 309 | ThreadTreeNode child = findInternalNode(head); | ||
| 310 | child.removeThread(tail, thread); | ||
| 311 | if (child.isThreadGroup() && child.getChildCount() < 1) { | ||
| 312 | // Prune non-leaf nodes with no children. | ||
| 313 | treeModel.removeNodeFromParent(child); | ||
| 314 | } | ||
| 315 | } | ||
| 316 | } | ||
| 317 | |||
| 318 | private ThreadTreeNode findLeafNode(ThreadReference thread, String name) { | ||
| 319 | for (int i = 0; i < getChildCount(); i++) { | ||
| 320 | ThreadTreeNode child = (ThreadTreeNode)getChildAt(i); | ||
| 321 | if (child.getThread() == thread) { | ||
| 322 | if (!name.equals(child.getName())) { | ||
| 323 | //### Assertion failure. | ||
| 324 | throw new RuntimeException("name mismatch"); | ||
| 325 | } | ||
| 326 | return child; | ||
| 327 | } | ||
| 328 | } | ||
| 329 | //### Assertion failure. | ||
| 330 | throw new RuntimeException("not found"); | ||
| 331 | } | ||
| 332 | |||
| 333 | private ThreadTreeNode findInternalNode(String name) { | ||
| 334 | for (int i = 0; i < getChildCount(); i++) { | ||
| 335 | ThreadTreeNode child = (ThreadTreeNode)getChildAt(i); | ||
| 336 | if (name.equals(child.getName())) { | ||
| 337 | return child; | ||
| 338 | } | ||
| 339 | } | ||
| 340 | //### Assertion failure. | ||
| 341 | throw new RuntimeException("not found"); | ||
| 342 | } | ||
| 343 | |||
| 344 | } | ||
| 345 | |||
| 346 | } | ||