1 /* 2 * Copyright (c) 2004, 2008, 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 package sun.tools.jconsole; 27 28 import java.awt.*; 29 import java.awt.event.*; 30 import java.beans.*; 31 import java.io.*; 32 import java.lang.reflect.*; 33 import java.util.*; 34 import java.util.List; 35 import java.util.Timer; 36 37 import javax.swing.*; 38 import javax.swing.plaf.*; 39 40 import com.sun.tools.jconsole.JConsolePlugin; 41 import com.sun.tools.jconsole.JConsoleContext; 42 import static com.sun.tools.jconsole.JConsoleContext.ConnectionState.*; 43 44 import static sun.tools.jconsole.ProxyClient.*; 45 46 @SuppressWarnings("serial") 47 public class VMPanel extends JTabbedPane implements PropertyChangeListener { 48 49 private ProxyClient proxyClient; 50 private Timer timer; 51 private int updateInterval; 52 private String hostName; 53 private int port; 54 private int vmid; 55 private String userName; 56 private String password; 57 private String url; 58 private VMInternalFrame vmIF = null; 59 private static final String windowsLaF = 60 "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"; 61 private static ArrayList<TabInfo> tabInfos = new ArrayList<TabInfo>(); 62 private boolean wasConnected = false; 63 64 // The everConnected flag keeps track of whether the window can be 65 // closed if the user clicks Cancel after a failed connection attempt. 66 // 67 private boolean everConnected = false; 68 69 // The initialUpdate flag is used to enable/disable tabs each time 70 // a connect or reconnect takes place. This flag avoids having to 71 // enable/disable tabs on each update call. 72 // 73 private boolean initialUpdate = true; 74 75 // Each VMPanel has its own instance of the JConsolePlugin 76 // A map of JConsolePlugin to the previous SwingWorker 77 private Map<JConsolePlugin, SwingWorker<?, ?>> plugins = null; 78 private boolean pluginTabsAdded = false; 79 80 // Update these only on the EDT 81 private JOptionPane optionPane; 82 private JProgressBar progressBar; 83 private long time0; 84 85 static { 86 tabInfos.add(new TabInfo(OverviewTab.class, OverviewTab.getTabName(), true)); 87 tabInfos.add(new TabInfo(MemoryTab.class, MemoryTab.getTabName(), true)); 88 tabInfos.add(new TabInfo(ThreadTab.class, ThreadTab.getTabName(), true)); 89 tabInfos.add(new TabInfo(ClassTab.class, ClassTab.getTabName(), true)); 90 tabInfos.add(new TabInfo(SummaryTab.class, SummaryTab.getTabName(), true)); 91 tabInfos.add(new TabInfo(MBeansTab.class, MBeansTab.getTabName(), true)); 92 } 93 94 public static TabInfo[] getTabInfos() { 95 return tabInfos.toArray(new TabInfo[tabInfos.size()]); 96 } 97 98 VMPanel(ProxyClient proxyClient, int updateInterval) { 99 this.proxyClient = proxyClient; 100 this.updateInterval = updateInterval; 101 this.hostName = proxyClient.getHostName(); 102 this.port = proxyClient.getPort(); 103 this.vmid = proxyClient.getVmid(); 104 this.userName = proxyClient.getUserName(); 105 this.password = proxyClient.getPassword(); 106 this.url = proxyClient.getUrl(); 107 108 for (TabInfo tabInfo : tabInfos) { 109 if (tabInfo.tabVisible) { 110 addTab(tabInfo); 111 } 112 } 113 114 plugins = new LinkedHashMap<JConsolePlugin, SwingWorker<?, ?>>(); 115 for (JConsolePlugin p : JConsole.getPlugins()) { 116 p.setContext(proxyClient); 117 plugins.put(p, null); 118 } 119 120 Utilities.updateTransparency(this); 121 122 ToolTipManager.sharedInstance().registerComponent(this); 123 124 // Start listening to connection state events 125 // 126 proxyClient.addPropertyChangeListener(this); 127 128 addMouseListener(new MouseAdapter() { 129 130 public void mouseClicked(MouseEvent e) { 131 if (connectedIconBounds != null && (e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0 && connectedIconBounds.contains(e.getPoint())) { 132 133 if (isConnected()) { 134 disconnect(); 135 wasConnected = false; 136 } else { 137 connect(); 138 } 139 repaint(); 140 } 141 } 142 }); 143 144 } 145 private static Icon connectedIcon16 = 146 new ImageIcon(VMPanel.class.getResource("resources/connected16.png")); 147 private static Icon connectedIcon24 = 148 new ImageIcon(VMPanel.class.getResource("resources/connected24.png")); 149 private static Icon disconnectedIcon16 = 150 new ImageIcon(VMPanel.class.getResource("resources/disconnected16.png")); 151 private static Icon disconnectedIcon24 = 152 new ImageIcon(VMPanel.class.getResource("resources/disconnected24.png")); 153 private Rectangle connectedIconBounds; 154 155 // Override to increase right inset for tab area, 156 // in order to reserve space for the connect toggle. 157 public void setUI(TabbedPaneUI ui) { 158 Insets insets = (Insets) UIManager.getLookAndFeelDefaults().get("TabbedPane.tabAreaInsets"); 159 insets = (Insets) insets.clone(); 160 insets.right += connectedIcon24.getIconWidth() + 8; 161 UIManager.put("TabbedPane.tabAreaInsets", insets); 162 super.setUI(ui); 163 } 164 165 // Override to paint the connect toggle 166 protected void paintComponent(Graphics g) { 167 super.paintComponent(g); 168 169 Icon icon; 170 Component c0 = getComponent(0); 171 if (c0 != null && c0.getY() > 24) { 172 icon = isConnected() ? connectedIcon24 : disconnectedIcon24; 173 } else { 174 icon = isConnected() ? connectedIcon16 : disconnectedIcon16; 175 } 176 Insets insets = getInsets(); 177 int x = getWidth() - insets.right - icon.getIconWidth() - 4; 178 int y = insets.top; 179 if (c0 != null) { 180 y = (c0.getY() - icon.getIconHeight()) / 2; 181 } 182 icon.paintIcon(this, g, x, y); 183 connectedIconBounds = new Rectangle(x, y, icon.getIconWidth(), icon.getIconHeight()); 184 } 185 186 public String getToolTipText(MouseEvent event) { 187 if (connectedIconBounds.contains(event.getPoint())) { 188 if (isConnected()) { 189 return getText("Connected. Click to disconnect."); 190 } else { 191 return getText("Disconnected. Click to connect."); 192 } 193 } else { 194 return super.getToolTipText(event); 195 } 196 } 197 198 private synchronized void addTab(TabInfo tabInfo) { 199 Tab tab = instantiate(tabInfo); 200 if (tab != null) { 201 addTab(tabInfo.name, tab); 202 } else { 203 tabInfo.tabVisible = false; 204 } 205 } 206 207 private synchronized void insertTab(TabInfo tabInfo, int index) { 208 Tab tab = instantiate(tabInfo); 209 if (tab != null) { 210 insertTab(tabInfo.name, null, tab, null, index); 211 } else { 212 tabInfo.tabVisible = false; 213 } 214 } 215 216 public synchronized void removeTabAt(int index) { 217 super.removeTabAt(index); 218 } 219 220 private Tab instantiate(TabInfo tabInfo) { 221 try { 222 Constructor con = tabInfo.tabClass.getConstructor(VMPanel.class); 223 return (Tab) con.newInstance(this); 224 } catch (Exception ex) { 225 System.err.println(ex); 226 return null; 227 } 228 } 229 230 boolean isConnected() { 231 return proxyClient.isConnected(); 232 } 233 234 public int getUpdateInterval() { 235 return updateInterval; 236 } 237 238 /** 239 * WARNING NEVER CALL THIS METHOD TO MAKE JMX REQUEST 240 * IF assertThread == false. 241 * DISPATCHER THREAD IS NOT ASSERTED. 242 * IT IS USED TO MAKE SOME LOCAL MANIPULATIONS. 243 */ 244 ProxyClient getProxyClient(boolean assertThread) { 245 if (assertThread) { 246 return getProxyClient(); 247 } else { 248 return proxyClient; 249 } 250 } 251 252 public ProxyClient getProxyClient() { 253 String threadClass = Thread.currentThread().getClass().getName(); 254 if (threadClass.equals("java.awt.EventDispatchThread")) { 255 String msg = "Calling VMPanel.getProxyClient() from the Event Dispatch Thread!"; 256 new RuntimeException(msg).printStackTrace(); 257 System.exit(1); 258 } 259 return proxyClient; 260 } 261 262 public void cleanUp() { 263 //proxyClient.disconnect(); 264 for (Tab tab : getTabs()) { 265 tab.dispose(); 266 } 267 for (JConsolePlugin p : plugins.keySet()) { 268 p.dispose(); 269 } 270 // Cancel pending update tasks 271 // 272 if (timer != null) { 273 timer.cancel(); 274 } 275 // Stop listening to connection state events 276 // 277 proxyClient.removePropertyChangeListener(this); 278 } 279 280 // Call on EDT 281 public void connect() { 282 if (isConnected()) { 283 // create plugin tabs if not done 284 createPluginTabs(); 285 // Notify tabs 286 fireConnectedChange(true); 287 // Enable/disable tabs on initial update 288 initialUpdate = true; 289 // Start/Restart update timer on connect/reconnect 290 startUpdateTimer(); 291 } else { 292 new Thread("VMPanel.connect") { 293 294 public void run() { 295 proxyClient.connect(); 296 } 297 }.start(); 298 } 299 } 300 301 // Call on EDT 302 public void disconnect() { 303 proxyClient.disconnect(); 304 updateFrameTitle(); 305 } 306 307 // Called on EDT 308 public void propertyChange(PropertyChangeEvent ev) { 309 String prop = ev.getPropertyName(); 310 311 if (prop == CONNECTION_STATE_PROPERTY) { 312 ConnectionState oldState = (ConnectionState) ev.getOldValue(); 313 ConnectionState newState = (ConnectionState) ev.getNewValue(); 314 switch (newState) { 315 case CONNECTING: 316 onConnecting(); 317 break; 318 319 case CONNECTED: 320 if (progressBar != null) { 321 progressBar.setIndeterminate(false); 322 progressBar.setValue(100); 323 } 324 closeOptionPane(); 325 updateFrameTitle(); 326 // create tabs if not done 327 createPluginTabs(); 328 repaint(); 329 // Notify tabs 330 fireConnectedChange(true); 331 // Enable/disable tabs on initial update 332 initialUpdate = true; 333 // Start/Restart update timer on connect/reconnect 334 startUpdateTimer(); 335 break; 336 337 case DISCONNECTED: 338 if (progressBar != null) { 339 progressBar.setIndeterminate(false); 340 progressBar.setValue(0); 341 closeOptionPane(); 342 } 343 vmPanelDied(); 344 if (oldState == ConnectionState.CONNECTED) { 345 // Notify tabs 346 fireConnectedChange(false); 347 } 348 break; 349 } 350 } 351 } 352 353 // Called on EDT 354 private void onConnecting() { 355 time0 = System.currentTimeMillis(); 356 357 final JConsole jc = (JConsole) SwingUtilities.getWindowAncestor(this); 358 359 String connectionName = getConnectionName(); 360 progressBar = new JProgressBar(); 361 progressBar.setIndeterminate(true); 362 JPanel progressPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); 363 progressPanel.add(progressBar); 364 365 Object[] message = { 366 "<html><h3>" + getText("connectingTo1", connectionName) + "</h3></html>", 367 progressPanel, 368 "<html><b>" + getText("connectingTo2", connectionName) + "</b></html>" 369 }; 370 371 optionPane = 372 SheetDialog.showOptionDialog(this, 373 message, 374 JOptionPane.DEFAULT_OPTION, 375 JOptionPane.INFORMATION_MESSAGE, null, 376 new String[]{getText("Cancel")}, 377 0); 378 379 380 } 381 382 // Called on EDT 383 private void closeOptionPane() { 384 if (optionPane != null) { 385 new Thread("VMPanel.sleeper") { 386 public void run() { 387 long elapsed = System.currentTimeMillis() - time0; 388 if (elapsed < 2000) { 389 try { 390 sleep(2000 - elapsed); 391 } catch (InterruptedException ex) { 392 // Ignore 393 } 394 } 395 SwingUtilities.invokeLater(new Runnable() { 396 397 public void run() { 398 optionPane.setVisible(false); 399 progressBar = null; 400 } 401 }); 402 } 403 }.start(); 404 } 405 } 406 407 void updateFrameTitle() { 408 VMInternalFrame vmIF = getFrame(); 409 if (vmIF != null) { 410 String displayName = getDisplayName(); 411 if (!proxyClient.isConnected()) { 412 displayName = getText("ConnectionName (disconnected)", displayName); 413 } 414 vmIF.setTitle(displayName); 415 } 416 } 417 418 private VMInternalFrame getFrame() { 419 if (vmIF == null) { 420 vmIF = (VMInternalFrame) SwingUtilities.getAncestorOfClass(VMInternalFrame.class, 421 this); 422 } 423 return vmIF; 424 } 425 426 // TODO: this method is not needed when all JConsole tabs 427 // are migrated to use the new JConsolePlugin API. 428 // 429 // A thread safe clone of all JConsole tabs 430 synchronized List<Tab> getTabs() { 431 ArrayList<Tab> list = new ArrayList<Tab>(); 432 int n = getTabCount(); 433 for (int i = 0; i < n; i++) { 434 Component c = getComponentAt(i); 435 if (c instanceof Tab) { 436 list.add((Tab) c); 437 } 438 } 439 return list; 440 } 441 442 private void startUpdateTimer() { 443 if (timer != null) { 444 timer.cancel(); 445 } 446 TimerTask timerTask = new TimerTask() { 447 448 public void run() { 449 update(); 450 } 451 }; 452 String timerName = "Timer-" + getConnectionName(); 453 timer = new Timer(timerName, true); 454 timer.schedule(timerTask, 0, updateInterval); 455 } 456 457 // Call on EDT 458 private void vmPanelDied() { 459 disconnect(); 460 461 final JConsole jc = (JConsole) SwingUtilities.getWindowAncestor(this); 462 463 JOptionPane optionPane; 464 465 final String connectStr = getText("Connect"); 466 final String reconnectStr = getText("Reconnect"); 467 final String cancelStr = getText("Cancel"); 468 469 String msgTitle, msgExplanation, buttonStr; 470 471 if (wasConnected) { 472 wasConnected = false; 473 msgTitle = getText("connectionLost1"); 474 msgExplanation = getText("connectionLost2", getConnectionName()); 475 buttonStr = reconnectStr; 476 } else { 477 msgTitle = getText("connectionFailed1"); 478 msgExplanation = getText("connectionFailed2", getConnectionName()); 479 buttonStr = connectStr; 480 } 481 482 optionPane = 483 SheetDialog.showOptionDialog(this, 484 "<html><h3>" + msgTitle + "</h3>" + 485 "<b>" + msgExplanation + "</b>", 486 JOptionPane.DEFAULT_OPTION, 487 JOptionPane.WARNING_MESSAGE, null, 488 new String[]{buttonStr, cancelStr}, 489 0); 490 491 optionPane.addPropertyChangeListener(new PropertyChangeListener() { 492 493 public void propertyChange(PropertyChangeEvent event) { 494 if (event.getPropertyName().equals(JOptionPane.VALUE_PROPERTY)) { 495 Object value = event.getNewValue(); 496 497 if (value == reconnectStr || value == connectStr) { 498 connect(); 499 } else if (!everConnected) { 500 try { 501 getFrame().setClosed(true); 502 } catch (PropertyVetoException ex) { 503 // Should not happen, but can be ignored. 504 } 505 } 506 } 507 } 508 }); 509 } 510 511 // Note: This method is called on a TimerTask thread. Any GUI manipulation 512 // must be performed with invokeLater() or invokeAndWait(). 513 private Object lockObject = new Object(); 514 515 private void update() { 516 synchronized (lockObject) { 517 if (!isConnected()) { 518 if (wasConnected) { 519 EventQueue.invokeLater(new Runnable() { 520 521 public void run() { 522 vmPanelDied(); 523 } 524 }); 525 } 526 wasConnected = false; 527 return; 528 } else { 529 wasConnected = true; 530 everConnected = true; 531 } 532 proxyClient.flush(); 533 List<Tab> tabs = getTabs(); 534 final int n = tabs.size(); 535 for (int i = 0; i < n; i++) { 536 final int index = i; 537 try { 538 if (!proxyClient.isDead()) { 539 // Update tab 540 // 541 tabs.get(index).update(); 542 // Enable tab on initial update 543 // 544 if (initialUpdate) { 545 EventQueue.invokeLater(new Runnable() { 546 547 public void run() { 548 setEnabledAt(index, true); 549 } 550 }); 551 } 552 } 553 } catch (Exception e) { 554 // Disable tab on initial update 555 // 556 if (initialUpdate) { 557 EventQueue.invokeLater(new Runnable() { 558 public void run() { 559 setEnabledAt(index, false); 560 } 561 }); 562 } 563 } 564 } 565 566 // plugin GUI update 567 for (JConsolePlugin p : plugins.keySet()) { 568 SwingWorker<?, ?> sw = p.newSwingWorker(); 569 SwingWorker<?, ?> prevSW = plugins.get(p); 570 // schedule SwingWorker to run only if the previous 571 // SwingWorker has finished its task and it hasn't started. 572 if (prevSW == null || prevSW.isDone()) { 573 if (sw == null || sw.getState() == SwingWorker.StateValue.PENDING) { 574 plugins.put(p, sw); 575 if (sw != null) { 576 sw.execute(); 577 } 578 } 579 } 580 } 581 582 // Set the first enabled tab in the tab's list 583 // as the selected tab on initial update 584 // 585 if (initialUpdate) { 586 EventQueue.invokeLater(new Runnable() { 587 public void run() { 588 // Select first enabled tab if current tab isn't. 589 int index = getSelectedIndex(); 590 if (index < 0 || !isEnabledAt(index)) { 591 for (int i = 0; i < n; i++) { 592 if (isEnabledAt(i)) { 593 setSelectedIndex(i); 594 break; 595 } 596 } 597 } 598 } 599 }); 600 initialUpdate = false; 601 } 602 } 603 } 604 605 public String getHostName() { 606 return hostName; 607 } 608 609 public int getPort() { 610 return port; 611 } 612 613 public String getUserName() { 614 return userName; 615 } 616 617 public String getUrl() { 618 return url; 619 } 620 621 public String getPassword() { 622 return password; 623 } 624 625 public String getConnectionName() { 626 return proxyClient.connectionName(); 627 } 628 629 public String getDisplayName() { 630 return proxyClient.getDisplayName(); 631 } 632 633 static class TabInfo { 634 635 Class<? extends Tab> tabClass; 636 String name; 637 boolean tabVisible; 638 639 TabInfo(Class<? extends Tab> tabClass, String name, boolean tabVisible) { 640 this.tabClass = tabClass; 641 this.name = name; 642 this.tabVisible = tabVisible; 643 } 644 } 645 646 // Convenience methods 647 private static String getText(String key, Object... args) { 648 return Resources.getText(key, args); 649 } 650 651 private void createPluginTabs() { 652 // add plugin tabs if not done 653 if (!pluginTabsAdded) { 654 for (JConsolePlugin p : plugins.keySet()) { 655 Map<String, JPanel> tabs = p.getTabs(); 656 for (Map.Entry<String, JPanel> e : tabs.entrySet()) { 657 addTab(e.getKey(), e.getValue()); 658 } 659 } 660 pluginTabsAdded = true; 661 } 662 } 663 664 private void fireConnectedChange(boolean connected) { 665 for (Tab tab : getTabs()) { 666 tab.firePropertyChange(JConsoleContext.CONNECTION_STATE_PROPERTY, !connected, connected); 667 } 668 } 669 }