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 }