/*
 * 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.
/** * 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); } } }