/* * Copyright (c) 2004, 2008, 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.inspector; import javax.swing.*; 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 javax.management.*; import javax.management.openmbean.CompositeData; import javax.management.openmbean.TabularData; import sun.tools.jconsole.JConsole; import sun.tools.jconsole.resources.Messages; @SuppressWarnings("serial") public class XMBeanNotifications extends JTable implements NotificationListener { private final static String[] columnNames = { Messages.TIME_STAMP, Messages.TYPE, Messages.USER_DATA, Messages.SEQ_NUM, Messages.MESSAGE, Messages.EVENT, Messages.SOURCE }; private HashMap listeners = new HashMap(); private volatile boolean subscribed; private XMBeanNotificationsListener currentListener; public final static String NOTIFICATION_RECEIVED_EVENT = "jconsole.xnotification.received"; private List 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"); private static TableCellEditor editor = new Utils.ReadOnlyTableCellEditor(new JTextField()); public XMBeanNotifications() { super(new TableSorter(columnNames, 0)); setColumnSelectionAllowed(false); setRowSelectionAllowed(false); getTableHeader().setReorderingAllowed(false); ArrayList l = new ArrayList(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 = Messages.DOUBLE_CLICK_TO_EXPAND_FORWARD_SLASH_COLLAPSE+ ". " + cell.toString(); } 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) { if (JConsole.isDebug()) { System.err.println("Error adding listener for delegate:"); e.printStackTrace(); } } } 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) { unregister(mbean); } } } catch (Exception e) { if (JConsole.isDebug()) { System.err.println("Error unregistering notification:"); e.printStackTrace(); } } } 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 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 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 + "]"; } 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 XMBean xmbean; private DefaultMutableTreeNode node; private volatile long received; private XMBeanNotifications notifications; private volatile boolean unregistered; private ArrayList data = new ArrayList(); public XMBeanNotificationsListener( XMBeanNotifications notifications, XMBean xmbean, DefaultMutableTreeNode node, String[] columnNames) { this.notifications = notifications; this.xmbean = xmbean; this.node = node; register(node); } public synchronized List getData() { return data; } public synchronized void clear() { data.clear(); received = 0; } public synchronized boolean isRegistered() { return !unregistered; } public synchronized void unregister() { try { xmbean.getMBeanServerConnection().removeNotificationListener( xmbean.getObjectName(), this, null, null); } catch (Exception e) { if (JConsole.isDebug()) { System.err.println("Error removing listener:"); e.printStackTrace(); } } unregistered = true; } public synchronized long getReceivedNotifications() { return received; } public synchronized void register(DefaultMutableTreeNode node) { clear(); this.node = node; try { xmbean.getMBeanServerConnection().addNotificationListener( xmbean.getObjectName(), this, null, null); unregistered = false; } catch (Exception e) { if (JConsole.isDebug()) { System.err.println("Error adding listener:"); e.printStackTrace(); } } } 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, xmbean, node, rowData, received); } catch (Exception e) { if (JConsole.isDebug()) { System.err.println("Error handling notification:"); e.printStackTrace(); } } } } }); } } }