/* * Copyright (c) 2004, 2007, 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.BorderLayout; import java.awt.GridLayout; import java.awt.Font; import java.text.SimpleDateFormat; import java.awt.FlowLayout; import java.awt.Component; import java.awt.EventQueue; import java.awt.event.*; import java.awt.Insets; 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.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 boolean subscribed; private XMBeanNotificationsListener currentListener; public final static String NOTIFICATION_RECEIVED_EVENT = "jconsole.xnotification.received"; private List notificationListenersList; private 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()); } public void cancelCellEditing() { TableCellEditor editor = getCellEditor(); if (editor != null) { editor.cancelCellEditing(); } } public void stopCellEditing() { TableCellEditor editor = getCellEditor(); if (editor != null) { editor.stopCellEditing(); } } public boolean isCellEditable(int row, int col) { UserDataCell cell = getUserDataCell(row, col); if (cell != null) { return cell.isMaximized(); } return true; } public void setValueAt(Object value, int row, int column) { } 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; } 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; } 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(new ObjectName("JMImplementation:type=MBeanServerDelegate"), this, null, null); subscribed = true; }catch(Exception e) { System.out.println("Error adding listener for delegate :"+ e.getMessage()); } } 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) { System.out.println("Error unregistering notification:"+ e.getMessage()); } } 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); } void fireNotificationReceived(XMBeanNotificationsListener listener, XMBean mbean, DefaultMutableTreeNode node, Object[] rowData, long received) { if(enabled) { DefaultTableModel tableModel = (DefaultTableModel) getModel(); if(listener == currentListener) { //tableModel.addRow(rowData); tableModel.insertRow(0, rowData); //tableModel.newDataAvailable(new TableModelEvent(tableModel)); repaint(); } } Notification notif = new Notification(NOTIFICATION_RECEIVED_EVENT, this, 0); notif.setUserData(new Long(received)); for(NotificationListener nl : notificationListenersList) nl.handleNotification(notif,node); } 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(); } 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; } 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); } } } public boolean isTableEditable() { return true; } public synchronized void emptyTable() { DefaultTableModel model = (DefaultTableModel)getModel(); //invalidate(); while (model.getRowCount()>0) model.removeRow(0); validate(); } 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)); } } 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); } 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 { 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 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()) { this.cancelCellEditing(); return true; } } } return super.stopCellEditing(); } } class XMBeanNotificationsListener implements NotificationListener { private XMBean xmbean; private DefaultMutableTreeNode node; private long received; private XMBeanNotifications notifications; private 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 boolean isRegistered() { return !unregistered; } public synchronized void unregister() { try { xmbean.getMBeanServerConnection(). removeNotificationListener(xmbean.getObjectName(),this,null,null); }catch(Exception e) { System.out.println("Error removing listener :"+ e.getMessage()); } unregistered = true; } public 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) { System.out.println("Error adding listener :"+ e.getMessage()); } } public synchronized void handleNotification(Notification e, Object handback) { try { if(unregistered) return; Date receivedDate = new Date(e.getTimeStamp()); String time = timeFormater.format(receivedDate); Object userData = e.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, e.getType(), (cell == null ? userData : cell), new Long(e.getSequenceNumber()), e.getMessage(), e, e.getSource()}; received++; data.add(0, rowData); notifications.fireNotificationReceived(this, xmbean, node, rowData, received); } catch (Exception ex) { ex.printStackTrace(); System.out.println("Error when handling notification :"+ ex.toString()); } } } }