/* * 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 tabInfos = new ArrayList(); 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> 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>(); 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 = { "

" + Resources.format(Messages.CONNECTING_TO1, connectionName) + "

", progressPanel, "" + Resources.format(Messages.CONNECTING_TO2, connectionName) + "" }; 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 getTabs() { ArrayList list = new ArrayList(); 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, "

" + msgTitle + "

" + "" + msgExplanation + "", 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 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 tabClass; String name; boolean tabVisible; TabInfo(Class 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 tabs = p.getTabs(); for (Map.Entry 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); } } }