/*
* Copyright (c) 2004, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.tools.jconsole;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.List;
import java.util.Timer;
import javax.swing.*;
import javax.swing.plaf.*;
import com.sun.tools.jconsole.JConsolePlugin;
import com.sun.tools.jconsole.JConsoleContext;
import static sun.tools.jconsole.ProxyClient.*;
@SuppressWarnings("serial")
public class VMPanel extends JTabbedPane implements PropertyChangeListener {
private ProxyClient proxyClient;
private Timer timer;
private int updateInterval;
private String hostName;
private int port;
private String userName;
private String password;
private String url;
private VMInternalFrame vmIF = null;
private static ArrayList<TabInfo> tabInfos = new ArrayList<TabInfo>();
private boolean wasConnected = false;
private boolean userDisconnected = false;
private boolean shouldUseSSL = true;
// The everConnected flag keeps track of whether the window can be
// closed if the user clicks Cancel after a failed connection attempt.
//
private boolean everConnected = false;
// The initialUpdate flag is used to enable/disable tabs each time
// a connect or reconnect takes place. This flag avoids having to
// enable/disable tabs on each update call.
//
private boolean initialUpdate = true;
// Each VMPanel has its own instance of the JConsolePlugin
// A map of JConsolePlugin to the previous SwingWorker
private Map<ExceptionSafePlugin, SwingWorker<?, ?>> plugins = null;
private boolean pluginTabsAdded = false;
// Update these only on the EDT
private JOptionPane optionPane;
private JProgressBar progressBar;
private long time0;
static {
tabInfos.add(new TabInfo(OverviewTab.class, OverviewTab.getTabName(), true));
tabInfos.add(new TabInfo(MemoryTab.class, MemoryTab.getTabName(), true));
tabInfos.add(new TabInfo(ThreadTab.class, ThreadTab.getTabName(), true));
tabInfos.add(new TabInfo(ClassTab.class, ClassTab.getTabName(), true));
tabInfos.add(new TabInfo(SummaryTab.class, SummaryTab.getTabName(), true));
tabInfos.add(new TabInfo(MBeansTab.class, MBeansTab.getTabName(), true));
}
public static TabInfo[] getTabInfos() {
return tabInfos.toArray(new TabInfo[tabInfos.size()]);
}
VMPanel(ProxyClient proxyClient, int updateInterval) {
this.proxyClient = proxyClient;
this.updateInterval = updateInterval;
this.hostName = proxyClient.getHostName();
this.port = proxyClient.getPort();
this.userName = proxyClient.getUserName();
this.password = proxyClient.getPassword();
this.url = proxyClient.getUrl();
for (TabInfo tabInfo : tabInfos) {
if (tabInfo.tabVisible) {
addTab(tabInfo);
}
}
plugins = new LinkedHashMap<ExceptionSafePlugin, SwingWorker<?, ?>>();
for (JConsolePlugin p : JConsole.getPlugins()) {
p.setContext(proxyClient);
plugins.put(new ExceptionSafePlugin(p), null);
}
Utilities.updateTransparency(this);
ToolTipManager.sharedInstance().registerComponent(this);
// Start listening to connection state events
//
proxyClient.addPropertyChangeListener(this);
addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (connectedIconBounds != null
&& (e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0
&& connectedIconBounds.contains(e.getPoint())) {
if (isConnected()) {
userDisconnected = true;
disconnect();
wasConnected = false;
} else {
connect();
}
repaint();
}
}
});
}
private static Icon connectedIcon16 =
new ImageIcon(VMPanel.class.getResource("resources/connected16.png"));
private static Icon connectedIcon24 =
new ImageIcon(VMPanel.class.getResource("resources/connected24.png"));
private static Icon disconnectedIcon16 =
new ImageIcon(VMPanel.class.getResource("resources/disconnected16.png"));
private static Icon disconnectedIcon24 =
new ImageIcon(VMPanel.class.getResource("resources/disconnected24.png"));
private Rectangle connectedIconBounds;
// Override to increase right inset for tab area,
// in order to reserve space for the connect toggle.
public void setUI(TabbedPaneUI ui) {
Insets insets = (Insets) UIManager.getLookAndFeelDefaults().get("TabbedPane.tabAreaInsets");
if (insets != null) {
insets = (Insets) insets.clone();
insets.right += connectedIcon24.getIconWidth() + 8;
UIManager.put("TabbedPane.tabAreaInsets", insets);
}
super.setUI(ui);
}
// Override to paint the connect toggle
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Icon icon;
Component c0 = getComponent(0);
if (c0 != null && c0.getY() > 24) {
icon = isConnected() ? connectedIcon24 : disconnectedIcon24;
} else {
icon = isConnected() ? connectedIcon16 : disconnectedIcon16;
}
Insets insets = getInsets();
int x = getWidth() - insets.right - icon.getIconWidth() - 4;
int y = insets.top;
if (c0 != null) {
y = (c0.getY() - icon.getIconHeight()) / 2;
}
icon.paintIcon(this, g, x, y);
connectedIconBounds = new Rectangle(x, y, icon.getIconWidth(), icon.getIconHeight());
}
public String getToolTipText(MouseEvent event) {
if (connectedIconBounds.contains(event.getPoint())) {
if (isConnected()) {
return Messages.CONNECTED_PUNCTUATION_CLICK_TO_DISCONNECT_;
} else {
return Messages.DISCONNECTED_PUNCTUATION_CLICK_TO_CONNECT_;
}
} else {
return super.getToolTipText(event);
}
}
private synchronized void addTab(TabInfo tabInfo) {
Tab tab = instantiate(tabInfo);
if (tab != null) {
addTab(tabInfo.name, tab);
} else {
tabInfo.tabVisible = false;
}
}
private synchronized void insertTab(TabInfo tabInfo, int index) {
Tab tab = instantiate(tabInfo);
if (tab != null) {
insertTab(tabInfo.name, null, tab, null, index);
} else {
tabInfo.tabVisible = false;
}
}
public synchronized void removeTabAt(int index) {
super.removeTabAt(index);
}
private Tab instantiate(TabInfo tabInfo) {
try {
Constructor<?> con = tabInfo.tabClass.getConstructor(VMPanel.class);
return (Tab) con.newInstance(this);
} catch (Exception ex) {
System.err.println(ex);
return null;
}
}
boolean isConnected() {
return proxyClient.isConnected();
}
public int getUpdateInterval() {
return updateInterval;
}
/**
* WARNING NEVER CALL THIS METHOD TO MAKE JMX REQUEST
* IF assertThread == false.
* DISPATCHER THREAD IS NOT ASSERTED.
* IT IS USED TO MAKE SOME LOCAL MANIPULATIONS.
*/
ProxyClient getProxyClient(boolean assertThread) {
if (assertThread) {
return getProxyClient();
} else {
return proxyClient;
}
}
public ProxyClient getProxyClient() {
String threadClass = Thread.currentThread().getClass().getName();
if (threadClass.equals("java.awt.EventDispatchThread")) {
String msg = "Calling VMPanel.getProxyClient() from the Event Dispatch Thread!";
new RuntimeException(msg).printStackTrace();
System.exit(1);
}
return proxyClient;
}
public void cleanUp() {
//proxyClient.disconnect();
for (Tab tab : getTabs()) {
tab.dispose();
}
for (JConsolePlugin p : plugins.keySet()) {
p.dispose();
}
// Cancel pending update tasks
//
if (timer != null) {
timer.cancel();
}
// Stop listening to connection state events
//
proxyClient.removePropertyChangeListener(this);
}
// Call on EDT
public void connect() {
if (isConnected()) {
// create plugin tabs if not done
createPluginTabs();
// Notify tabs
fireConnectedChange(true);
// Enable/disable tabs on initial update
initialUpdate = true;
// Start/Restart update timer on connect/reconnect
startUpdateTimer();
} else {
new Thread("VMPanel.connect") {
public void run() {
proxyClient.connect(shouldUseSSL);
}
}.start();
}
}
// Call on EDT
public void disconnect() {
proxyClient.disconnect();
updateFrameTitle();
}
// Called on EDT
public void propertyChange(PropertyChangeEvent ev) {
String prop = ev.getPropertyName();
if (prop == CONNECTION_STATE_PROPERTY) {
ConnectionState oldState = (ConnectionState) ev.getOldValue();
ConnectionState newState = (ConnectionState) ev.getNewValue();
switch (newState) {
case CONNECTING:
onConnecting();
break;
case CONNECTED:
if (progressBar != null) {
progressBar.setIndeterminate(false);
progressBar.setValue(100);
}
closeOptionPane();
updateFrameTitle();
// create tabs if not done
createPluginTabs();
repaint();
// Notify tabs
fireConnectedChange(true);
// Enable/disable tabs on initial update
initialUpdate = true;
// Start/Restart update timer on connect/reconnect
startUpdateTimer();
break;
case DISCONNECTED:
if (progressBar != null) {
progressBar.setIndeterminate(false);
progressBar.setValue(0);
closeOptionPane();
}
vmPanelDied();
if (oldState == ConnectionState.CONNECTED) {
// Notify tabs
fireConnectedChange(false);
}
break;
}
}
}
// Called on EDT
private void onConnecting() {
time0 = System.currentTimeMillis();
SwingUtilities.getWindowAncestor(this);
String connectionName = getConnectionName();
progressBar = new JProgressBar();
progressBar.setIndeterminate(true);
JPanel progressPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
progressPanel.add(progressBar);
Object[] message = {
"<html><h3>" + Resources.format(Messages.CONNECTING_TO1, connectionName) + "</h3></html>",
progressPanel,
"<html><b>" + Resources.format(Messages.CONNECTING_TO2, connectionName) + "</b></html>"
};
optionPane =
SheetDialog.showOptionDialog(this,
message,
JOptionPane.DEFAULT_OPTION,
JOptionPane.INFORMATION_MESSAGE, null,
new String[]{Messages.CANCEL},
0);
}
// Called on EDT
private void closeOptionPane() {
if (optionPane != null) {
new Thread("VMPanel.sleeper") {
public void run() {
long elapsed = System.currentTimeMillis() - time0;
if (elapsed < 2000) {
try {
sleep(2000 - elapsed);
} catch (InterruptedException ex) {
// Ignore
}
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
optionPane.setVisible(false);
progressBar = null;
}
});
}
}.start();
}
}
void updateFrameTitle() {
VMInternalFrame vmIF = getFrame();
if (vmIF != null) {
String displayName = getDisplayName();
if (!proxyClient.isConnected()) {
displayName = Resources.format(Messages.CONNECTION_NAME__DISCONNECTED_, displayName);
}
vmIF.setTitle(displayName);
}
}
private VMInternalFrame getFrame() {
if (vmIF == null) {
vmIF = (VMInternalFrame) SwingUtilities.getAncestorOfClass(VMInternalFrame.class,
this);
}
return vmIF;
}
// TODO: this method is not needed when all JConsole tabs
// are migrated to use the new JConsolePlugin API.
//
// A thread safe clone of all JConsole tabs
synchronized List<Tab> getTabs() {
ArrayList<Tab> list = new ArrayList<Tab>();
int n = getTabCount();
for (int i = 0; i < n; i++) {
Component c = getComponentAt(i);
if (c instanceof Tab) {
list.add((Tab) c);
}
}
return list;
}
private void startUpdateTimer() {
if (timer != null) {
timer.cancel();
}
TimerTask timerTask = new TimerTask() {
public void run() {
update();
}
};
String timerName = "Timer-" + getConnectionName();
timer = new Timer(timerName, true);
timer.schedule(timerTask, 0, updateInterval);
}
// Call on EDT
private void vmPanelDied() {
disconnect();
if (userDisconnected) {
userDisconnected = false;
return;
}
JOptionPane optionPane;
String msgTitle, msgExplanation, buttonStr;
if (wasConnected) {
wasConnected = false;
msgTitle = Messages.CONNECTION_LOST1;
msgExplanation = Resources.format(Messages.CONNECTING_TO2, getConnectionName());
buttonStr = Messages.RECONNECT;
} else if (shouldUseSSL) {
msgTitle = Messages.CONNECTION_FAILED_SSL1;
msgExplanation = Resources.format(Messages.CONNECTION_FAILED_SSL2, getConnectionName());
buttonStr = Messages.INSECURE;
} else {
msgTitle = Messages.CONNECTION_FAILED1;
msgExplanation = Resources.format(Messages.CONNECTION_FAILED2, getConnectionName());
buttonStr = Messages.CONNECT;
}
optionPane =
SheetDialog.showOptionDialog(this,
"<html><h3>" + msgTitle + "</h3>" +
"<b>" + msgExplanation + "</b>",
JOptionPane.DEFAULT_OPTION,
JOptionPane.WARNING_MESSAGE, null,
new String[]{buttonStr, Messages.CANCEL},
0);
optionPane.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
if (event.getPropertyName().equals(JOptionPane.VALUE_PROPERTY)) {
Object value = event.getNewValue();
if (value == Messages.RECONNECT || value == Messages.CONNECT) {
connect();
} else if (value == Messages.INSECURE) {
shouldUseSSL = false;
connect();
} else if (!everConnected) {
try {
getFrame().setClosed(true);
} catch (PropertyVetoException ex) {
// Should not happen, but can be ignored.
}
}
}
}
});
}
// Note: This method is called on a TimerTask thread. Any GUI manipulation
// must be performed with invokeLater() or invokeAndWait().
private Object lockObject = new Object();
private void update() {
synchronized (lockObject) {
if (!isConnected()) {
if (wasConnected) {
EventQueue.invokeLater(new Runnable() {
public void run() {
vmPanelDied();
}
});
}
wasConnected = false;
return;
} else {
wasConnected = true;
everConnected = true;
}
proxyClient.flush();
List<Tab> tabs = getTabs();
final int n = tabs.size();
for (int i = 0; i < n; i++) {
final int index = i;
try {
if (!proxyClient.isDead()) {
// Update tab
//
tabs.get(index).update();
// Enable tab on initial update
//
if (initialUpdate) {
EventQueue.invokeLater(new Runnable() {
public void run() {
setEnabledAt(index, true);
}
});
}
}
} catch (Exception e) {
// Disable tab on initial update
//
if (initialUpdate) {
EventQueue.invokeLater(new Runnable() {
public void run() {
setEnabledAt(index, false);
}
});
}
}
}
// plugin GUI update
for (ExceptionSafePlugin p : plugins.keySet()) {
SwingWorker<?, ?> sw = p.newSwingWorker();
SwingWorker<?, ?> prevSW = plugins.get(p);
// schedule SwingWorker to run only if the previous
// SwingWorker has finished its task and it hasn't started.
if (prevSW == null || prevSW.isDone()) {
if (sw == null || sw.getState() == SwingWorker.StateValue.PENDING) {
plugins.put(p, sw);
if (sw != null) {
p.executeSwingWorker(sw);
}
}
}
}
// Set the first enabled tab in the tab's list
// as the selected tab on initial update
//
if (initialUpdate) {
EventQueue.invokeLater(new Runnable() {
public void run() {
// Select first enabled tab if current tab isn't.
int index = getSelectedIndex();
if (index < 0 || !isEnabledAt(index)) {
for (int i = 0; i < n; i++) {
if (isEnabledAt(i)) {
setSelectedIndex(i);
break;
}
}
}
}
});
initialUpdate = false;
}
}
}
public String getHostName() {
return hostName;
}
public int getPort() {
return port;
}
public String getUserName() {
return userName;
}
public String getUrl() {
return url;
}
public String getPassword() {
return password;
}
public String getConnectionName() {
return proxyClient.connectionName();
}
public String getDisplayName() {
return proxyClient.getDisplayName();
}
static class TabInfo {
Class<? extends Tab> tabClass;
String name;
boolean tabVisible;
TabInfo(Class<? extends Tab> tabClass, String name, boolean tabVisible) {
this.tabClass = tabClass;
this.name = name;
this.tabVisible = tabVisible;
}
}
private void createPluginTabs() {
// add plugin tabs if not done
if (!pluginTabsAdded) {
for (JConsolePlugin p : plugins.keySet()) {
Map<String, JPanel> tabs = p.getTabs();
for (Map.Entry<String, JPanel> e : tabs.entrySet()) {
addTab(e.getKey(), e.getValue());
}
}
pluginTabsAdded = true;
}
}
private void fireConnectedChange(boolean connected) {
for (Tab tab : getTabs()) {
tab.firePropertyChange(JConsoleContext.CONNECTION_STATE_PROPERTY, !connected, connected);
}
}
}