/* * Copyright (c) 2007, 2011, 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 com.sun.tools.visualvm.modules.mbeans; import javax.swing.*; import javax.swing.event.*; import javax.swing.table.*; import javax.swing.tree.*; import java.awt.Font; import java.text.SimpleDateFormat; import java.awt.Component; import java.awt.EventQueue; import java.awt.event.*; import java.awt.Dimension; import java.util.*; import java.io.*; import java.lang.reflect.Array; import java.util.logging.Level; import java.util.logging.Logger; import javax.management.*; import javax.management.openmbean.CompositeData; import javax.management.openmbean.TabularData; class XMBeanNotifications extends JTable implements NotificationListener { private final static String[] columnNames = { Resources.getText("LBL_TimeStamp"), // NOI18N Resources.getText("LBL_Type"), // NOI18N Resources.getText("LBL_UserData"), // NOI18N Resources.getText("LBL_SeqNum"), // NOI18N Resources.getText("LBL_Message"), // NOI18N Resources.getText("LBL_Event"), // NOI18N Resources.getText("LBL_Source")}; // NOI18N private final static Logger LOGGER = Logger.getLogger(XMBeanNotifications.class.getName()); private HashMap<ObjectName, XMBeanNotificationsListener> listeners = new HashMap<ObjectName, XMBeanNotificationsListener>(); private volatile boolean subscribed; private XMBeanNotificationsListener currentListener; public final static String NOTIFICATION_RECEIVED_EVENT = "jconsole.xnotification.received"; // NOI18N private List<NotificationListener> notificationListenersList; private volatile boolean enabled; private Font normalFont, boldFont; private int rowMinHeight = -1; private TableCellEditor userDataEditor = new UserDataCellEditor(); private NotifMouseListener mouseListener = new NotifMouseListener(); private SimpleDateFormat timeFormater = new SimpleDateFormat("HH:mm:ss:SSS"); // NOI18N private static TableCellEditor editor = new Utils.ReadOnlyTableCellEditor(new JTextField()); public XMBeanNotifications() { super(new TableSorter(columnNames,0)); setColumnSelectionAllowed(false); setRowSelectionAllowed(false); getTableHeader().setReorderingAllowed(false); ArrayList<NotificationListener> l = new ArrayList<NotificationListener>(1); notificationListenersList = Collections.synchronizedList(l); addMouseListener(mouseListener); TableColumnModel colModel = getColumnModel(); colModel.getColumn(0).setPreferredWidth(45); colModel.getColumn(1).setPreferredWidth(50); colModel.getColumn(2).setPreferredWidth(50); colModel.getColumn(3).setPreferredWidth(40); colModel.getColumn(4).setPreferredWidth(50); colModel.getColumn(5).setPreferredWidth(50); setColumnEditors(); addKeyListener(new Utils.CopyKeyAdapter()); } // Call on EDT public void cancelCellEditing() { TableCellEditor tce = getCellEditor(); if (tce != null) { tce.cancelCellEditing(); } } // Call on EDT public void stopCellEditing() { TableCellEditor tce = getCellEditor(); if (tce != null) { tce.stopCellEditing(); } } // Call on EDT @Override public boolean isCellEditable(int row, int col) { UserDataCell cell = getUserDataCell(row, col); if (cell != null) { return cell.isMaximized(); } return true; } // Call on EDT @Override public void setValueAt(Object value, int row, int column) { } // Call on EDT @Override public synchronized Component prepareRenderer( TableCellRenderer renderer, int row, int column) { //In case we have a repaint thread that is in the process of //repainting an obsolete table, just ignore the call. //It can happen when MBean selection is switched at a very quick rate if (row >= getRowCount()) return null; Component comp = super.prepareRenderer(renderer, row, column); if (normalFont == null) { normalFont = comp.getFont(); boldFont = normalFont.deriveFont(Font.BOLD); } UserDataCell cell = getUserDataCell(row, 2); if (column == 2 && cell != null) { comp.setFont(boldFont); int size = cell.getHeight(); if (size > 0) { if(getRowHeight(row) != size) setRowHeight(row, size); } } else { comp.setFont(normalFont); } return comp; } // Call on EDT @Override public synchronized TableCellRenderer getCellRenderer(int row, int column) { //In case we have a repaint thread that is in the process of //repainting an obsolete table, just ignore the call. //It can happen when MBean selection is switched at a very quick rate if (row >= getRowCount()) return null; DefaultTableCellRenderer renderer; String toolTip = null; UserDataCell cell = getUserDataCell(row, column); if (cell != null && cell.isInited()) { renderer = (DefaultTableCellRenderer) cell.getRenderer(); } else { renderer = (DefaultTableCellRenderer) super.getCellRenderer(row, column); } if (cell != null) toolTip = Resources.getText("LBL_DoubleClickToExpandCollapse") + // NOI18N ". " + cell.toString(); // NOI18N else { Object val = ((DefaultTableModel) getModel()).getValueAt(row, column); if (val != null) toolTip = val.toString(); } renderer.setToolTipText(toolTip); return renderer; } // Call on EDT private UserDataCell getUserDataCell(int row, int column) { Object obj = ((DefaultTableModel) getModel()).getValueAt(row,column); if (obj instanceof UserDataCell) return (UserDataCell) obj; return null; } synchronized void dispose() { listeners.clear(); } public long getReceivedNotifications(XMBean mbean) { XMBeanNotificationsListener listener = listeners.get(mbean.getObjectName()); if (listener == null) return 0; else return listener.getReceivedNotifications(); } public synchronized boolean clearCurrentNotifications() { emptyTable(); if (currentListener != null) { currentListener.clear(); return true; } else return false; } public synchronized boolean unregisterListener(DefaultMutableTreeNode node) { XMBean mbean = (XMBean) ((XNodeInfo) node.getUserObject()).getData(); return unregister(mbean.getObjectName()); } public synchronized void registerListener(DefaultMutableTreeNode node) throws InstanceNotFoundException, IOException { XMBean mbean = (XMBean) ((XNodeInfo) node.getUserObject()).getData(); if (!subscribed) { try { mbean.getMBeanServerConnection().addNotificationListener( MBeanServerDelegate.DELEGATE_NAME, this, null, null); subscribed = true; } catch (Exception e) { LOGGER.log(Level.SEVERE, "Error adding listener for delegate", e); // NOI18N } } XMBeanNotificationsListener listener = listeners.get(mbean.getObjectName()); if (listener == null) { listener = new XMBeanNotificationsListener( this, mbean, node, columnNames); listeners.put(mbean.getObjectName(), listener); } else { if (!listener.isRegistered()) { emptyTable(); listener.register(node); } } enabled = true; currentListener = listener; } public synchronized void handleNotification( Notification notif, Object handback) { try { if (notif instanceof MBeanServerNotification) { ObjectName mbean = ((MBeanServerNotification) notif).getMBeanName(); if (notif.getType().indexOf("JMX.mbean.unregistered") >= 0) { // NOI18N unregister(mbean); } } } catch(Exception e) { LOGGER.log(Level.SEVERE, "Error unregistering notification", e); // NOI18N } } public synchronized void disableNotifications() { emptyTable(); currentListener = null; enabled = false; } private synchronized boolean unregister(ObjectName mbean) { XMBeanNotificationsListener listener = listeners.get(mbean); if (listener != null && listener.isRegistered()) { listener.unregister(); return true; } else return false; } public void addNotificationsListener(NotificationListener nl) { notificationListenersList.add(nl); } public void removeNotificationsListener(NotificationListener nl) { notificationListenersList.remove(nl); } // Call on EDT void fireNotificationReceived( XMBeanNotificationsListener listener, XMBean mbean, DefaultMutableTreeNode node, Object[] rowData, long received) { if (enabled) { DefaultTableModel tableModel = (DefaultTableModel) getModel(); if (listener == currentListener) { tableModel.insertRow(0, rowData); repaint(); } } Notification notif = new Notification(NOTIFICATION_RECEIVED_EVENT, this, 0); notif.setUserData(received); for (NotificationListener nl : notificationListenersList) nl.handleNotification(notif, node); } // Call on EDT private void updateModel(List<Object[]> data) { emptyTable(); DefaultTableModel tableModel = (DefaultTableModel) getModel(); for (Object[] rowData : data) tableModel.addRow(rowData); } public synchronized boolean isListenerRegistered(XMBean mbean) { XMBeanNotificationsListener listener = listeners.get(mbean.getObjectName()); if (listener == null) return false; return listener.isRegistered(); } // Call on EDT public synchronized void loadNotifications(XMBean mbean) { XMBeanNotificationsListener listener = listeners.get(mbean.getObjectName()); emptyTable(); if (listener != null) { enabled = true; List<Object[]> data = listener.getData(); updateModel(data); currentListener = listener; validate(); repaint(); } else enabled = false; } // Call on EDT private void setColumnEditors() { TableColumnModel tcm = getColumnModel(); for (int i = 0; i < columnNames.length; i++) { TableColumn tc = tcm.getColumn(i); if (i == 2) { tc.setCellEditor(userDataEditor); } else { tc.setCellEditor(editor); } } } // Call on EDT public boolean isTableEditable() { return true; } // Call on EDT public synchronized void emptyTable() { DefaultTableModel model = (DefaultTableModel) getModel(); //invalidate(); while (model.getRowCount() > 0) model.removeRow(0); validate(); } // Call on EDT synchronized void updateUserDataCell(int row, int col) { Object obj = getModel().getValueAt(row, 2); if (obj instanceof UserDataCell) { UserDataCell cell = (UserDataCell) obj; if (!cell.isInited()) { if (rowMinHeight == -1) rowMinHeight = getRowHeight(row); cell.init(super.getCellRenderer(row, col), rowMinHeight); } cell.switchState(); setRowHeight(row, cell.getHeight()); if(!cell.isMaximized()) { cancelCellEditing(); //Back to simple editor. editCellAt(row, 2); } invalidate(); repaint(); } } class UserDataCellRenderer extends DefaultTableCellRenderer { Component comp; UserDataCellRenderer(Component comp) { this.comp = comp; Dimension d = comp.getPreferredSize(); if (d.getHeight() > 200) { comp.setPreferredSize(new Dimension((int) d.getWidth(), 200)); } } @Override public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { return comp; } public Component getComponent() { return comp; } } class UserDataCell { TableCellRenderer minRenderer; UserDataCellRenderer maxRenderer; int minHeight; boolean minimized = true; boolean init = false; Object userData; UserDataCell(Object userData, Component max) { this.userData = userData; this.maxRenderer = new UserDataCellRenderer(max); } @Override public String toString() { if (userData == null) return null; if (userData.getClass().isArray()) { String name = Utils.getArrayClassName(userData.getClass().getName()); int length = Array.getLength(userData); return name + "[" + length +"]"; // NOI18N } if (userData instanceof CompositeData || userData instanceof TabularData) return userData.getClass().getName(); return userData.toString(); } boolean isInited() { return init; } void init(TableCellRenderer minRenderer, int minHeight) { this.minRenderer = minRenderer; this.minHeight = minHeight; init = true; } void switchState() { minimized = !minimized; } boolean isMaximized() { return !minimized; } void minimize() { minimized = true; } void maximize() { minimized = false; } int getHeight() { if (minimized) return minHeight; else return (int) maxRenderer.getComponent(). getPreferredSize().getHeight(); } TableCellRenderer getRenderer() { if (minimized) return minRenderer; else return maxRenderer; } } class NotifMouseListener extends MouseAdapter { @Override public void mousePressed(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { if (e.getClickCount() >= 2) { int row = XMBeanNotifications.this.getSelectedRow(); int col = XMBeanNotifications.this.getSelectedColumn(); if (col != 2) return; if (col == -1 || row == -1) return; XMBeanNotifications.this.updateUserDataCell(row, col); } } } } class UserDataCellEditor extends XTextFieldEditor { // implements javax.swing.table.TableCellEditor @Override public Component getTableCellEditorComponent( JTable table, Object value, boolean isSelected, int row, int column) { Object val = value; if (column == 2) { Object obj = getModel().getValueAt(row, column); if (obj instanceof UserDataCell) { UserDataCell cell = (UserDataCell) obj; if (cell.getRenderer() instanceof UserDataCellRenderer) { UserDataCellRenderer zr = (UserDataCellRenderer) cell.getRenderer(); return zr.getComponent(); } } else { Component comp = super.getTableCellEditorComponent( table, val, isSelected, row, column); textField.setEditable(false); return comp; } } return super.getTableCellEditorComponent( table, val, isSelected, row, column); } @Override public boolean stopCellEditing() { int editingRow = getEditingRow(); int editingColumn = getEditingColumn(); if (editingColumn == 2) { Object obj = getModel().getValueAt(editingRow, editingColumn); if (obj instanceof UserDataCell) { UserDataCell cell = (UserDataCell) obj; if (cell.isMaximized()) { cancelCellEditing(); return true; } } } return super.stopCellEditing(); } } class XMBeanNotificationsListener implements NotificationListener { private String[] columnNames; private XMBean mbean; private DefaultMutableTreeNode node; private volatile long received; private XMBeanNotifications notifications; private volatile boolean unregistered; private ArrayList<Object[]> data = new ArrayList<Object[]>(); public XMBeanNotificationsListener( XMBeanNotifications notifications, XMBean mbean, DefaultMutableTreeNode node, String[] columnNames) { this.notifications = notifications; this.mbean = mbean; this.node = node; this.columnNames = columnNames; register(node); } public synchronized List<Object[]> getData() { return data; } public synchronized void clear() { data.clear(); received = 0; } public synchronized boolean isRegistered() { return !unregistered; } public synchronized void unregister() { try { mbean.getMBeanServerConnection().removeNotificationListener( mbean.getObjectName(), this, null, null); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Error removing listener", e); // NOI18N } unregistered = true; } public synchronized long getReceivedNotifications() { return received; } public synchronized void register(DefaultMutableTreeNode node) { clear(); this.node = node; try { mbean.getMBeanServerConnection().addNotificationListener( mbean.getObjectName(), this, null, null); unregistered = false; } catch (Exception e) { LOGGER.log(Level.SEVERE, "Error adding listener", e); // NOI18N } } public synchronized void handleNotification( final Notification n, Object hb) { EventQueue.invokeLater(new Runnable() { public void run() { synchronized (XMBeanNotificationsListener.this) { try { if (unregistered) return; Date receivedDate = new Date(n.getTimeStamp()); String time = timeFormater.format(receivedDate); Object userData = n.getUserData(); Component comp = null; UserDataCell cell = null; if ((comp = XDataViewer.createNotificationViewer(userData)) != null) { XDataViewer.registerForMouseEvent(comp, mouseListener); cell = new UserDataCell(userData, comp); } Object[] rowData = { time, n.getType(), (cell == null ? userData : cell), n.getSequenceNumber(), n.getMessage(), n, n.getSource() }; received++; data.add(0, rowData); notifications.fireNotificationReceived( XMBeanNotificationsListener.this, mbean, node, rowData, received); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Error handling notification", e); // NOI18N } } } }); } } }