/*
* 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.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<ObjectName, XMBeanNotificationsListener> listeners =
new HashMap<ObjectName, XMBeanNotificationsListener>();
private volatile boolean subscribed;
private XMBeanNotificationsListener currentListener;
public final static String NOTIFICATION_RECEIVED_EVENT =
"jconsole.xnotification.received";
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");
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 = 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<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 + "]";
}
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<Object[]> data = new ArrayList<Object[]>();
public XMBeanNotificationsListener(
XMBeanNotifications notifications,
XMBean xmbean,
DefaultMutableTreeNode node,
String[] columnNames) {
this.notifications = notifications;
this.xmbean = xmbean;
this.node = node;
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 {
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();
}
}
}
}
});
}
}
}