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 }