|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
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()); |
|
} |
|
|
|
|
|
public void cancelCellEditing() { |
|
TableCellEditor tce = getCellEditor(); |
|
if (tce != null) { |
|
tce.cancelCellEditing(); |
|
} |
|
} |
|
|
|
|
|
public void stopCellEditing() { |
|
TableCellEditor tce = getCellEditor(); |
|
if (tce != null) { |
|
tce.stopCellEditing(); |
|
} |
|
} |
|
|
|
|
|
@Override |
|
public boolean isCellEditable(int row, int col) { |
|
UserDataCell cell = getUserDataCell(row, col); |
|
if (cell != null) { |
|
return cell.isMaximized(); |
|
} |
|
return true; |
|
} |
|
|
|
|
|
@Override |
|
public void setValueAt(Object value, int row, int column) { |
|
} |
|
|
|
|
|
@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. |
|
|
|
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; |
|
} |
|
|
|
|
|
@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. |
|
|
|
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( |
|
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); |
|
} |
|
|
|
|
|
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); |
|
} |
|
} |
|
|
|
|
|
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(); |
|
} |
|
|
|
|
|
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; |
|
} |
|
} |
|
|
|
|
|
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(); |
|
|
|
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(); |
|
|
|
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 { |
|
|
|
@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(); |
|
} |
|
} |
|
} |
|
} |
|
}); |
|
} |
|
} |
|
} |