/*******************************************************************************
* Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
******************************************************************************/
package org.eclipse.persistence.tools.workbench.uitools;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Iterator;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import org.eclipse.persistence.tools.workbench.uitools.app.PropertyValueModel;
import org.eclipse.persistence.tools.workbench.uitools.app.SimplePropertyValueModel;
import org.eclipse.persistence.tools.workbench.uitools.app.ValueModel;
import org.eclipse.persistence.tools.workbench.uitools.app.swing.CheckBoxModelAdapter;
import org.eclipse.persistence.tools.workbench.utility.SynchronizedBoolean;
import org.eclipse.persistence.tools.workbench.utility.iterators.ArrayIterator;
import org.eclipse.persistence.tools.workbench.utility.iterators.FilteringIterator;
import org.eclipse.persistence.tools.workbench.utility.iterators.NullIterator;
import org.eclipse.persistence.tools.workbench.utility.iterators.TransformationIterator;
/**
* This is a simple development tool for browsing the current set
* of Threads and ThreadGroups.
*
* NB: This browser is primarily for development-time use only. It would
* need to be refactored for use by end-users (strings would need to
* be "externalized", behavior made more configurable, etc.).
*/
public class ThreadBrowser {
private DefaultTreeModel treeModel;
private JTree tree; // we want to set the "expansion" state
private SynchronizedBoolean synchronizedAutoRefresh;
private Thread autoRefreshThread;
private JFrame browser;
private static final ThreadGroup[] EMPTY_GROUPS = new ThreadGroup[0];
private static final Comparator THREAD_GROUP_COMPARATOR =
new Comparator() {
public int compare(Object o1, Object o2) {
return Collator.getInstance().compare(((ThreadGroup) o1).getName(), ((ThreadGroup) o2).getName());
}
};
private static final Thread[] EMPTY_THREADS = new Thread[0];
private static final Comparator THREAD_COMPARATOR =
new Comparator() {
public int compare(Object o1, Object o2) {
return Collator.getInstance().compare(((Thread) o1).getName(), ((Thread) o2).getName());
}
};
public static void main(String[] args) throws Exception {
new ThreadBrowser().exec(args);
}
public ThreadBrowser() {
super();
this.initialize();
}
// ********** initialization **********
private void initialize() {
this.treeModel = this.buildTreeModel();
this.tree = this.buildTree();
this.expandAll();
this.synchronizedAutoRefresh = new SynchronizedBoolean(false);
this.autoRefreshThread = this.buildAutoRefreshThread();
this.autoRefreshThread.start();
this.browser = this.buildBrowser();
}
private Thread buildAutoRefreshThread() {
return new Thread(this.buildAutoRefreshRunnable(), "Thread Browser Auto-Refresh");
}
private Runnable buildAutoRefreshRunnable() {
return new AutoRefreshRunnable(this.synchronizedAutoRefresh, this.buildRefreshRunnable());
}
private Runnable buildRefreshRunnable() {
return new Runnable() {
public void run() {
ThreadBrowser.this.refresh();
}
};
}
private DefaultTreeModel buildTreeModel() {
return new DefaultTreeModel(this.buildRootNode(), true);
}
private TreeNode buildRootNode() {
return this.buildTreeNode(this.systemRootThreadGroup());
}
/**
* Return the system "root" thread group.
* All other thread groups and threads are under this thread group.
*/
private ThreadGroup systemRootThreadGroup() {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
ThreadGroup parent = tg.getParent();
while (parent != null) {
tg = parent;
parent = tg.getParent();
}
return tg;
}
/**
* Build and return a tree node for the specified thread group,
* with all its children populated.
*/
private DefaultMutableTreeNode buildTreeNode(ThreadGroup tg) {
DefaultMutableTreeNode node = new DefaultMutableTreeNode(tg, true);
ThreadGroup[] subGroups = this.subGroupsOf(tg);
for (int i = 0; i < subGroups.length; i++) {
node.add(this.buildTreeNode(subGroups[i])); // recurse
}
Thread[] threads = this.threadsOf(tg);
for (int i = 0; i < threads.length; i++) {
node.add(this.buildTreeNode(threads[i]));
}
return node;
}
/**
* Return the specified thread group's sub thread groups.
* @see ThreadGroup#enumerate(ThreadGroup[], boolean)
*/
private ThreadGroup[] subGroupsOf(ThreadGroup tg) {
int estimatedSize = tg.activeGroupCount();
if (estimatedSize == 0) {
return EMPTY_GROUPS;
}
ThreadGroup[] subGroups;
int subGroupsCopied;
do {
estimatedSize += estimatedSize;
subGroups = new ThreadGroup[estimatedSize];
subGroupsCopied = tg.enumerate(subGroups, false);
} while (estimatedSize <= subGroupsCopied);
ThreadGroup[] result = new ThreadGroup[subGroupsCopied];
System.arraycopy(subGroups, 0, result, 0, subGroupsCopied);
Arrays.sort(result, THREAD_GROUP_COMPARATOR);
return result;
}
/**
* Return the specified thread group's threads.
* @see ThreadGroup#enumerate(Thread[], boolean)
*/
private Thread[] threadsOf(ThreadGroup tg) {
int estimatedSize = tg.activeCount();
if (estimatedSize == 0) {
return EMPTY_THREADS;
}
Thread[] threads;
int threadsCopied;
do {
estimatedSize += estimatedSize;
threads = new Thread[estimatedSize];
threadsCopied = tg.enumerate(threads, false);
} while (estimatedSize <= threadsCopied);
Thread[] result = new Thread[threadsCopied];
System.arraycopy(threads, 0, result, 0, threadsCopied);
Arrays.sort(result, THREAD_COMPARATOR);
return result;
}
/**
* Build and return a tree node for the specified thread.
*/
private DefaultMutableTreeNode buildTreeNode(Thread thread) {
return new DefaultMutableTreeNode(thread, false);
}
private JTree buildTree() {
JTree result = new LocalTree(this.treeModel);
result.setRootVisible(true);
result.setShowsRootHandles(true);
result.setRowHeight(20);
result.setDoubleBuffered(true);
return result;
}
private JFrame buildBrowser() {
JFrame window = new JFrame("Thread Browser");
window.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
window.getContentPane().add(this.buildMainPanel(), "Center");
window.setLocation(300, 300);
window.setSize(400, 400);
window.addWindowListener(this.buildWindowListener());
return window;
}
private Component buildMainPanel() {
JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.add(this.buildTreePane(), BorderLayout.CENTER);
mainPanel.add(this.buildControlPanel(), BorderLayout.SOUTH);
return mainPanel;
}
private Component buildTreePane() {
return new JScrollPane(this.tree);
}
private Component buildControlPanel() {
GridLayout grid = new GridLayout(1,0);
grid.setHgap(5);
JPanel controlPanel = new JPanel(grid);
controlPanel.add(this.buildDumpSelectedGroupsButton());
controlPanel.add(this.buildRefreshButton());
controlPanel.add(this.buildAutoRefreshCheckBox());
controlPanel.setBorder(BorderFactory.createEmptyBorder(2, 0, 0, 0));
return controlPanel;
}
private WindowListener buildWindowListener() {
return new WindowAdapter() {
public void windowClosed(WindowEvent e) {
super.windowClosed(e);
ThreadBrowser.this.interruptAutoRefreshThread();
}
};
}
// ********** buttons **********
private JButton buildDumpSelectedGroupsButton() {
return new JButton(this.buildDumpSelectedGroupsAction());
}
private Action buildDumpSelectedGroupsAction() {
Action action = new AbstractAction("dump") {
public void actionPerformed(ActionEvent event) {
ThreadBrowser.this.dumpSelectedGroups();
}
};
action.setEnabled(true);
return action;
}
void dumpSelectedGroups() {
for (Iterator stream = this.selectedThreadGroups(); stream.hasNext(); ) {
((ThreadGroup) stream.next()).list();
}
}
private JButton buildRefreshButton() {
return new JButton(this.buildRefreshAction());
}
private Action buildRefreshAction() {
Action action = new AbstractAction("refresh") {
public void actionPerformed(ActionEvent event) {
ThreadBrowser.this.refresh();
}
};
action.setEnabled(true);
return action;
}
void refresh() {
this.hook();
this.treeModel.setRoot(this.buildRootNode());
this.expandAll();
}
/**
* this is just a hook where you can put something that
* you want to execute every few seconds or so ~bjv
*/
private void hook() {
// Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
// System.out.println((window == null) ? "null" : window.getName());
}
private JCheckBox buildAutoRefreshCheckBox() {
JCheckBox checkBox = new JCheckBox();
checkBox.setText("auto refresh");
checkBox.setHorizontalAlignment(SwingConstants.CENTER);
PropertyValueModel autoRefreshHolder = new SimplePropertyValueModel(Boolean.valueOf(this.synchronizedAutoRefresh.getValue()));
autoRefreshHolder.addPropertyChangeListener(ValueModel.VALUE, this.buildAutoRefreshListener());
checkBox.setModel(new CheckBoxModelAdapter(autoRefreshHolder));
return checkBox;
}
private PropertyChangeListener buildAutoRefreshListener() {
return new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
ThreadBrowser.this.setAutoRefresh(((Boolean) evt.getNewValue()).booleanValue());
}
};
}
// ********** queries **********
private Iterator threadGroupPaths() {
Collection threadGroupPaths = new ArrayList();
this.addThreadGroupPathsTo((DefaultMutableTreeNode) this.treeModel.getRoot(), threadGroupPaths);
return threadGroupPaths.iterator();
}
// private Iterator selectedThreads() {
// return new FilteringIterator(this.selectedValues()) {
// protected boolean accept(Object next) {
// return next instanceof Thread;
// }
// };
// }
//
private Iterator selectedThreadGroups() {
return new FilteringIterator(this.selectedValues()) {
protected boolean accept(Object next) {
return next instanceof ThreadGroup;
}
};
}
private Iterator selectedValues() {
return new TransformationIterator(this.selectedPaths()) {
protected Object transform(Object next) {
return ((DefaultMutableTreeNode) ((TreePath) next).getLastPathComponent()).getUserObject();
}
};
}
private Iterator selectedPaths() {
TreePath[] paths = this.tree.getSelectionPaths();
return (paths == null) ? NullIterator.instance() : new ArrayIterator(paths);
}
// ********** behavior **********
private void exec(String[] args) throws Exception {
this.browser.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.open();
}
public void open() {
this.browser.setVisible(true);
}
private void expandAll() {
for (Iterator stream = this.threadGroupPaths(); stream.hasNext(); ) {
this.tree.expandPath((TreePath) stream.next());
}
}
private void addThreadGroupPathsTo(DefaultMutableTreeNode node, Collection threadGroupPaths) {
if (node.getUserObject() instanceof ThreadGroup) {
threadGroupPaths.add(new TreePath(node.getPath()));
for (Enumeration stream = node.children(); stream.hasMoreElements(); ) {
DefaultMutableTreeNode child = (DefaultMutableTreeNode) stream.nextElement();
this.addThreadGroupPathsTo(child, threadGroupPaths); // recurse
}
}
}
void setAutoRefresh(boolean autoRefresh) {
this.synchronizedAutoRefresh.setValue(autoRefresh);
}
void interruptAutoRefreshThread() {
this.autoRefreshThread.interrupt();
}
// ********** nested classes **********
/**
* pretty up the nodes' text
*/
private static class LocalTree extends JTree {
LocalTree(TreeModel treeModel) {
super(treeModel);
}
public String convertValueToText(Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
Object userObject = node.getUserObject();
if (userObject instanceof Thread) {
return this.convertToText((Thread) userObject);
}
return this.convertToText((ThreadGroup) userObject);
}
private String convertToText(Thread thread) {
StringBuffer sb = new StringBuffer();
if (thread == Thread.currentThread()) {
sb.append('*');
}
sb.append(thread.getName());
sb.append(" [priority=");
sb.append(thread.getPriority());
sb.append("]");
if (thread.isDaemon()) {
sb.append(" - daemon");
}
return sb.toString();
}
private String convertToText(ThreadGroup threadGroup) {
StringBuffer sb = new StringBuffer();
sb.append(threadGroup.getName());
sb.append(" [max priority=");
sb.append(threadGroup.getMaxPriority());
sb.append("]");
return sb.toString();
}
}
/**
* This runnable will execute until interrupted. If the auto-refresh flag
* is set to true, this runnable will execute a "refresh runnable" every
* 5 seconds by dispatching it to the AWT Event Queue.
*/
private static class AutoRefreshRunnable implements Runnable {
private SynchronizedBoolean synchronizedAutoRefresh;
private Runnable refreshRunnable;
AutoRefreshRunnable(SynchronizedBoolean synchronizedAutoRefresh, Runnable refreshRunnable) {
super();
this.synchronizedAutoRefresh = synchronizedAutoRefresh;
this.refreshRunnable = refreshRunnable;
}
public void run() {
while (true) {
try {
Thread.sleep(5000);
this.synchronizedAutoRefresh.waitUntilTrue();
} catch (InterruptedException ex) {
// we were interrupted while sleeping or waiting, must be quittin' time
return;
}
EventQueue.invokeLater(this.refreshRunnable);
}
}
}
}