001: /*
002: * Copyright 2004-2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.tools.jconsole;
027:
028: import java.awt.*;
029: import java.awt.event.*;
030: import java.beans.*;
031: import java.io.*;
032: import java.lang.reflect.*;
033: import java.util.*;
034: import java.util.List;
035: import java.util.Timer;
036:
037: import javax.swing.*;
038: import javax.swing.plaf.*;
039:
040: import com.sun.tools.jconsole.JConsolePlugin;
041: import com.sun.tools.jconsole.JConsoleContext;
042: import static com.sun.tools.jconsole.JConsoleContext.ConnectionState.*;
043:
044: import static sun.tools.jconsole.ProxyClient.*;
045:
046: @SuppressWarnings("serial")
047: public class VMPanel extends JTabbedPane implements
048: PropertyChangeListener {
049: private ProxyClient proxyClient;
050: private Timer timer;
051: private int updateInterval;
052: private String hostName;
053: private int port;
054: private int vmid;
055: private String userName;
056: private String password;
057: private String url;
058: private VMInternalFrame vmIF = null;
059:
060: private static final String windowsLaF = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
061:
062: private static ArrayList<TabInfo> tabInfos = new ArrayList<TabInfo>();
063:
064: private boolean wasConnected = false;
065:
066: // The everConnected flag keeps track of whether the window can be
067: // closed if the user clicks Cancel after a failed connection attempt.
068: //
069: private boolean everConnected = false;
070:
071: // The initialUpdate flag is used to enable/disable tabs each time
072: // a connect or reconnect takes place. This flag avoids having to
073: // enable/disable tabs on each update call.
074: //
075: private boolean initialUpdate = true;
076:
077: // Each VMPanel has its own instance of the JConsolePlugin
078: // A map of JConsolePlugin to the previous SwingWorker
079: private Map<JConsolePlugin, SwingWorker<?, ?>> plugins = null;
080: private boolean pluginTabsAdded = false;
081:
082: // Update these only on the EDT
083: private JOptionPane optionPane;
084: private JProgressBar progressBar;
085: private long time0;
086:
087: static {
088: tabInfos.add(new TabInfo(OverviewTab.class, OverviewTab
089: .getTabName(), true));
090: tabInfos.add(new TabInfo(MemoryTab.class, MemoryTab
091: .getTabName(), true));
092: tabInfos.add(new TabInfo(ThreadTab.class, ThreadTab
093: .getTabName(), true));
094: tabInfos.add(new TabInfo(ClassTab.class, ClassTab.getTabName(),
095: true));
096: tabInfos.add(new TabInfo(SummaryTab.class, SummaryTab
097: .getTabName(), true));
098: tabInfos.add(new TabInfo(MBeansTab.class, MBeansTab
099: .getTabName(), true));
100: }
101:
102: public static TabInfo[] getTabInfos() {
103: return tabInfos.toArray(new TabInfo[tabInfos.size()]);
104: }
105:
106: VMPanel(ProxyClient proxyClient, int updateInterval) {
107: this .proxyClient = proxyClient;
108: this .updateInterval = updateInterval;
109: this .hostName = proxyClient.getHostName();
110: this .port = proxyClient.getPort();
111: this .vmid = proxyClient.getVmid();
112: this .userName = proxyClient.getUserName();
113: this .password = proxyClient.getPassword();
114: this .url = proxyClient.getUrl();
115:
116: for (TabInfo tabInfo : tabInfos) {
117: if (tabInfo.tabVisible) {
118: addTab(tabInfo);
119: }
120: }
121:
122: plugins = new LinkedHashMap<JConsolePlugin, SwingWorker<?, ?>>();
123: for (JConsolePlugin p : JConsole.getPlugins()) {
124: p.setContext(proxyClient);
125: plugins.put(p, null);
126: }
127:
128: Utilities.updateTransparency(this );
129:
130: ToolTipManager.sharedInstance().registerComponent(this );
131:
132: // Start listening to connection state events
133: //
134: proxyClient.addPropertyChangeListener(this );
135:
136: addMouseListener(new MouseAdapter() {
137: public void mouseClicked(MouseEvent e) {
138: if (connectedIconBounds != null
139: && (e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0
140: && connectedIconBounds.contains(e.getPoint())) {
141:
142: if (isConnected()) {
143: disconnect();
144: wasConnected = false;
145: } else {
146: connect();
147: }
148: repaint();
149: }
150: }
151: });
152:
153: }
154:
155: private static Icon connectedIcon16 = new ImageIcon(VMPanel.class
156: .getResource("resources/connected16.png"));
157: private static Icon connectedIcon24 = new ImageIcon(VMPanel.class
158: .getResource("resources/connected24.png"));
159: private static Icon disconnectedIcon16 = new ImageIcon(
160: VMPanel.class.getResource("resources/disconnected16.png"));
161: private static Icon disconnectedIcon24 = new ImageIcon(
162: VMPanel.class.getResource("resources/disconnected24.png"));
163:
164: private Rectangle connectedIconBounds;
165:
166: // Override to increase right inset for tab area,
167: // in order to reserve space for the connect toggle.
168: public void setUI(TabbedPaneUI ui) {
169: Insets insets = (Insets) UIManager.getLookAndFeelDefaults()
170: .get("TabbedPane.tabAreaInsets");
171: insets = (Insets) insets.clone();
172: insets.right += connectedIcon24.getIconWidth() + 8;
173: UIManager.put("TabbedPane.tabAreaInsets", insets);
174: super .setUI(ui);
175: }
176:
177: // Override to paint the connect toggle
178: protected void paintComponent(Graphics g) {
179: super .paintComponent(g);
180:
181: Icon icon;
182: Component c0 = getComponent(0);
183: if (c0 != null && c0.getY() > 24) {
184: icon = isConnected() ? connectedIcon24 : disconnectedIcon24;
185: } else {
186: icon = isConnected() ? connectedIcon16 : disconnectedIcon16;
187: }
188: Insets insets = getInsets();
189: int x = getWidth() - insets.right - icon.getIconWidth() - 4;
190: int y = insets.top;
191: if (c0 != null) {
192: y = (c0.getY() - icon.getIconHeight()) / 2;
193: }
194: icon.paintIcon(this , g, x, y);
195: connectedIconBounds = new Rectangle(x, y, icon.getIconWidth(),
196: icon.getIconHeight());
197: }
198:
199: public String getToolTipText(MouseEvent event) {
200: if (connectedIconBounds.contains(event.getPoint())) {
201: if (isConnected()) {
202: return getText("Connected. Click to disconnect.");
203: } else {
204: return getText("Disconnected. Click to connect.");
205: }
206: } else {
207: return super .getToolTipText(event);
208: }
209: }
210:
211: private synchronized void addTab(TabInfo tabInfo) {
212: Tab tab = instantiate(tabInfo);
213: if (tab != null) {
214: addTab(tabInfo.name, tab);
215: } else {
216: tabInfo.tabVisible = false;
217: }
218: }
219:
220: private synchronized void insertTab(TabInfo tabInfo, int index) {
221: Tab tab = instantiate(tabInfo);
222: if (tab != null) {
223: insertTab(tabInfo.name, null, tab, null, index);
224: } else {
225: tabInfo.tabVisible = false;
226: }
227: }
228:
229: public synchronized void removeTabAt(int index) {
230: super .removeTabAt(index);
231: }
232:
233: private Tab instantiate(TabInfo tabInfo) {
234: try {
235: Constructor con = tabInfo.tabClass
236: .getConstructor(VMPanel.class);
237: return (Tab) con.newInstance(this );
238: } catch (Exception ex) {
239: System.err.println(ex);
240: return null;
241: }
242: }
243:
244: boolean isConnected() {
245: return proxyClient.isConnected();
246: }
247:
248: public int getUpdateInterval() {
249: return updateInterval;
250: }
251:
252: /**
253: * WARNING NEVER CALL THIS METHOD TO MAKE JMX REQUEST
254: * IF assertThread == false.
255: * DISPATCHER THREAD IS NOT ASSERTED.
256: * IT IS USED TO MAKE SOME LOCAL MANIPULATIONS.
257: */
258: ProxyClient getProxyClient(boolean assertThread) {
259: if (assertThread)
260: return getProxyClient();
261: else
262: return proxyClient;
263: }
264:
265: public ProxyClient getProxyClient() {
266: String threadClass = Thread.currentThread().getClass()
267: .getName();
268: if (threadClass.equals("java.awt.EventDispatchThread")) {
269: String msg = "Calling VMPanel.getProxyClient() from the Event Dispatch Thread!";
270: new RuntimeException(msg).printStackTrace();
271: System.exit(1);
272: }
273: return proxyClient;
274: }
275:
276: public void cleanUp() {
277: //proxyClient.disconnect();
278: for (Tab tab : getTabs()) {
279: tab.dispose();
280: }
281: for (JConsolePlugin p : plugins.keySet()) {
282: p.dispose();
283: }
284: // Cancel pending update tasks
285: //
286: if (timer != null) {
287: timer.cancel();
288: }
289: // Stop listening to connection state events
290: //
291: proxyClient.removePropertyChangeListener(this );
292: }
293:
294: // Call on EDT
295: public void connect() {
296: if (isConnected()) {
297: // create plugin tabs if not done
298: createPluginTabs();
299: // Notify tabs
300: fireConnectedChange(true);
301: // Enable/disable tabs on initial update
302: initialUpdate = true;
303: // Start/Restart update timer on connect/reconnect
304: startUpdateTimer();
305: } else {
306: new Thread("VMPanel.connect") {
307: public void run() {
308: proxyClient.connect();
309: }
310: }.start();
311: }
312: }
313:
314: // Call on EDT
315: public void disconnect() {
316: proxyClient.disconnect();
317: updateFrameTitle();
318: }
319:
320: // Called on EDT
321: public void propertyChange(PropertyChangeEvent ev) {
322: String prop = ev.getPropertyName();
323:
324: if (prop == CONNECTION_STATE_PROPERTY) {
325: ConnectionState oldState = (ConnectionState) ev
326: .getOldValue();
327: ConnectionState newState = (ConnectionState) ev
328: .getNewValue();
329: switch (newState) {
330: case CONNECTING:
331: onConnecting();
332: break;
333:
334: case CONNECTED:
335: if (progressBar != null) {
336: progressBar.setIndeterminate(false);
337: progressBar.setValue(100);
338: }
339: closeOptionPane();
340: updateFrameTitle();
341: // create tabs if not done
342: createPluginTabs();
343: repaint();
344: // Notify tabs
345: fireConnectedChange(true);
346: // Enable/disable tabs on initial update
347: initialUpdate = true;
348: // Start/Restart update timer on connect/reconnect
349: startUpdateTimer();
350: break;
351:
352: case DISCONNECTED:
353: if (progressBar != null) {
354: progressBar.setIndeterminate(false);
355: progressBar.setValue(0);
356: closeOptionPane();
357: }
358: vmPanelDied();
359: if (oldState == ConnectionState.CONNECTED) {
360: // Notify tabs
361: fireConnectedChange(false);
362: }
363: break;
364: }
365: }
366: }
367:
368: // Called on EDT
369: private void onConnecting() {
370: time0 = System.currentTimeMillis();
371:
372: final JConsole jc = (JConsole) SwingUtilities
373: .getWindowAncestor(this );
374:
375: String connectionName = getConnectionName();
376: progressBar = new JProgressBar();
377: progressBar.setIndeterminate(true);
378: JPanel progressPanel = new JPanel(new FlowLayout(
379: FlowLayout.CENTER));
380: progressPanel.add(progressBar);
381:
382: Object[] message = {
383: "<html><h3>" + getText("connectingTo1", connectionName)
384: + "</h3></html>",
385: progressPanel,
386: "<html><b>" + getText("connectingTo2", connectionName)
387: + "</b></html>" };
388:
389: optionPane = SheetDialog.showOptionDialog(this , message,
390: JOptionPane.DEFAULT_OPTION,
391: JOptionPane.INFORMATION_MESSAGE, null,
392: new String[] { getText("Cancel") }, 0);
393:
394: }
395:
396: // Called on EDT
397: private void closeOptionPane() {
398: if (optionPane != null) {
399: new Thread("VMPanel.sleeper") {
400: public void run() {
401: long elapsed = System.currentTimeMillis() - time0;
402: if (elapsed < 2000) {
403: try {
404: sleep(2000 - elapsed);
405: } catch (InterruptedException ex) {
406: // Ignore
407: }
408: }
409: SwingUtilities.invokeLater(new Runnable() {
410: public void run() {
411: optionPane.setVisible(false);
412: progressBar = null;
413: }
414: });
415: }
416: }.start();
417: }
418: }
419:
420: void updateFrameTitle() {
421: VMInternalFrame vmIF = getFrame();
422: if (vmIF != null) {
423: String displayName = getDisplayName();
424: if (!proxyClient.isConnected()) {
425: displayName = getText("ConnectionName (disconnected)",
426: displayName);
427: }
428: vmIF.setTitle(displayName);
429: }
430: }
431:
432: private VMInternalFrame getFrame() {
433: if (vmIF == null) {
434: vmIF = (VMInternalFrame) SwingUtilities.getAncestorOfClass(
435: VMInternalFrame.class, this );
436: }
437: return vmIF;
438: }
439:
440: // TODO: this method is not needed when all JConsole tabs
441: // are migrated to use the new JConsolePlugin API.
442: //
443: // A thread safe clone of all JConsole tabs
444: synchronized List<Tab> getTabs() {
445: ArrayList<Tab> list = new ArrayList<Tab>();
446: int n = getTabCount();
447: for (int i = 0; i < n; i++) {
448: Component c = getComponentAt(i);
449: if (c instanceof Tab) {
450: list.add((Tab) c);
451: }
452: }
453: return list;
454: }
455:
456: private void startUpdateTimer() {
457: if (timer != null) {
458: timer.cancel();
459: }
460: TimerTask timerTask = new TimerTask() {
461: public void run() {
462: update();
463: }
464: };
465: String timerName = "Timer-" + getConnectionName();
466: timer = new Timer(timerName, true);
467: timer.schedule(timerTask, 0, updateInterval);
468: }
469:
470: // Call on EDT
471: private void vmPanelDied() {
472: disconnect();
473:
474: final JConsole jc = (JConsole) SwingUtilities
475: .getWindowAncestor(this );
476:
477: JOptionPane optionPane;
478:
479: final String connectStr = getText("Connect");
480: final String reconnectStr = getText("Reconnect");
481: final String cancelStr = getText("Cancel");
482:
483: String msgTitle, msgExplanation, buttonStr;
484:
485: if (wasConnected) {
486: wasConnected = false;
487: msgTitle = getText("connectionLost1");
488: msgExplanation = getText("connectionLost2",
489: getConnectionName());
490: buttonStr = reconnectStr;
491: } else {
492: msgTitle = getText("connectionFailed1");
493: msgExplanation = getText("connectionFailed2",
494: getConnectionName());
495: buttonStr = connectStr;
496: }
497:
498: optionPane = SheetDialog.showOptionDialog(this , "<html><h3>"
499: + msgTitle + "</h3>" + "<b>" + msgExplanation + "</b>",
500: JOptionPane.DEFAULT_OPTION,
501: JOptionPane.WARNING_MESSAGE, null, new String[] {
502: buttonStr, cancelStr }, 0);
503:
504: optionPane
505: .addPropertyChangeListener(new PropertyChangeListener() {
506: public void propertyChange(PropertyChangeEvent event) {
507: if (event.getPropertyName().equals(
508: JOptionPane.VALUE_PROPERTY)) {
509: Object value = event.getNewValue();
510:
511: if (value == reconnectStr
512: || value == connectStr) {
513: connect();
514: } else if (!everConnected) {
515: try {
516: getFrame().setClosed(true);
517: } catch (PropertyVetoException ex) {
518: // Should not happen, but can be ignored.
519: }
520: }
521: }
522: }
523: });
524: }
525:
526: // Note: This method is called on a TimerTask thread. Any GUI manipulation
527: // must be performed with invokeLater() or invokeAndWait().
528: private Object lockObject = new Object();
529:
530: private void update() {
531: synchronized (lockObject) {
532: if (!isConnected()) {
533: if (wasConnected) {
534: EventQueue.invokeLater(new Runnable() {
535: public void run() {
536: vmPanelDied();
537: }
538: });
539: }
540: wasConnected = false;
541: return;
542: } else {
543: wasConnected = true;
544: everConnected = true;
545: }
546: proxyClient.flush();
547: List<Tab> tabs = getTabs();
548: final int n = tabs.size();
549: for (int i = 0; i < n; i++) {
550: final int index = i;
551: try {
552: if (!proxyClient.isDead()) {
553: // Update tab
554: //
555: tabs.get(index).update();
556: // Enable tab on initial update
557: //
558: if (initialUpdate) {
559: EventQueue.invokeLater(new Runnable() {
560: public void run() {
561: setEnabledAt(index, true);
562: }
563: });
564: }
565: }
566: } catch (Exception e) {
567: // Disable tab on initial update
568: //
569: if (initialUpdate) {
570: EventQueue.invokeLater(new Runnable() {
571: public void run() {
572: setEnabledAt(index, false);
573: }
574: });
575: }
576: }
577: }
578:
579: // plugin GUI update
580: for (JConsolePlugin p : plugins.keySet()) {
581: SwingWorker<?, ?> sw = p.newSwingWorker();
582: SwingWorker<?, ?> prevSW = plugins.get(p);
583: // schedule SwingWorker to run only if the previous
584: // SwingWorker has finished its task and it hasn't started.
585: if (prevSW == null || prevSW.isDone()) {
586: if (sw == null
587: || sw.getState() == SwingWorker.StateValue.PENDING) {
588: plugins.put(p, sw);
589: if (sw != null) {
590: sw.execute();
591: }
592: }
593: }
594: }
595:
596: // Set the first enabled tab in the tabīs list
597: // as the selected tab on initial update
598: //
599: if (initialUpdate) {
600: EventQueue.invokeLater(new Runnable() {
601: public void run() {
602: // Select first enabled tab if current tab isn't.
603: int index = getSelectedIndex();
604: if (index < 0 || !isEnabledAt(index)) {
605: for (int i = 0; i < n; i++) {
606: if (isEnabledAt(i)) {
607: setSelectedIndex(i);
608: break;
609: }
610: }
611: }
612: }
613: });
614: initialUpdate = false;
615: }
616: }
617: }
618:
619: public String getHostName() {
620: return hostName;
621: }
622:
623: public int getPort() {
624: return port;
625: }
626:
627: public String getUserName() {
628: return userName;
629: }
630:
631: public String getUrl() {
632: return url;
633: }
634:
635: public String getPassword() {
636: return password;
637: }
638:
639: public String getConnectionName() {
640: return proxyClient.connectionName();
641: }
642:
643: public String getDisplayName() {
644: return proxyClient.getDisplayName();
645: }
646:
647: static class TabInfo {
648: Class<? extends Tab> tabClass;
649: String name;
650: boolean tabVisible;
651:
652: TabInfo(Class<? extends Tab> tabClass, String name,
653: boolean tabVisible) {
654: this .tabClass = tabClass;
655: this .name = name;
656: this .tabVisible = tabVisible;
657: }
658: }
659:
660: // Convenience methods
661: private static String getText(String key, Object... args) {
662: return Resources.getText(key, args);
663: }
664:
665: private void createPluginTabs() {
666: // add plugin tabs if not done
667: if (!pluginTabsAdded) {
668: for (JConsolePlugin p : plugins.keySet()) {
669: Map<String, JPanel> tabs = p.getTabs();
670: for (Map.Entry<String, JPanel> e : tabs.entrySet()) {
671: addTab(e.getKey(), e.getValue());
672: }
673: }
674: pluginTabsAdded = true;
675: }
676: }
677:
678: private void fireConnectedChange(boolean connected) {
679: for (Tab tab : getTabs()) {
680: tab.firePropertyChange(
681: JConsoleContext.CONNECTION_STATE_PROPERTY,
682: !connected, connected);
683: }
684: }
685: }
|