1 /* 2 * Copyright (c) 2004, 2012, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.tools.jconsole.inspector; 27 28 import javax.swing.*; 29 import javax.swing.table.*; 30 import javax.swing.tree.*; 31 import java.awt.Font; 32 33 import java.text.SimpleDateFormat; 34 35 import java.awt.Component; 36 import java.awt.EventQueue; 37 import java.awt.event.*; 38 import java.awt.Dimension; 39 import java.util.*; 40 import java.io.*; 41 import java.lang.reflect.Array; 42 43 import javax.management.*; 44 import javax.management.openmbean.CompositeData; 45 import javax.management.openmbean.TabularData; 46 47 import sun.tools.jconsole.JConsole; 48 import sun.tools.jconsole.Messages; 49 50 @SuppressWarnings("serial") 51 public class XMBeanNotifications extends JTable implements NotificationListener { 52 53 private final static String[] columnNames = { 54 Messages.TIME_STAMP, 55 Messages.TYPE, 56 Messages.USER_DATA, 57 Messages.SEQ_NUM, 58 Messages.MESSAGE, 59 Messages.EVENT, 60 Messages.SOURCE 61 }; 62 private HashMap<ObjectName, XMBeanNotificationsListener> listeners = 63 new HashMap<ObjectName, XMBeanNotificationsListener>(); 64 private volatile boolean subscribed; 65 private XMBeanNotificationsListener currentListener; 66 public final static String NOTIFICATION_RECEIVED_EVENT = 67 "jconsole.xnotification.received"; 68 private List<NotificationListener> notificationListenersList; 69 private volatile boolean enabled; 70 private Font normalFont, boldFont; 71 private int rowMinHeight = -1; 72 private TableCellEditor userDataEditor = new UserDataCellEditor(); 73 private NotifMouseListener mouseListener = new NotifMouseListener(); 74 private SimpleDateFormat timeFormater = new SimpleDateFormat("HH:mm:ss:SSS"); 75 private static TableCellEditor editor = 76 new Utils.ReadOnlyTableCellEditor(new JTextField()); 77 XMBeanNotifications()78 public XMBeanNotifications() { 79 super(new TableSorter(columnNames, 0)); 80 setColumnSelectionAllowed(false); 81 setRowSelectionAllowed(false); 82 getTableHeader().setReorderingAllowed(false); 83 ArrayList<NotificationListener> l = 84 new ArrayList<NotificationListener>(1); 85 notificationListenersList = Collections.synchronizedList(l); 86 87 addMouseListener(mouseListener); 88 89 TableColumnModel colModel = getColumnModel(); 90 colModel.getColumn(0).setPreferredWidth(45); 91 colModel.getColumn(1).setPreferredWidth(50); 92 colModel.getColumn(2).setPreferredWidth(50); 93 colModel.getColumn(3).setPreferredWidth(40); 94 colModel.getColumn(4).setPreferredWidth(50); 95 colModel.getColumn(5).setPreferredWidth(50); 96 setColumnEditors(); 97 addKeyListener(new Utils.CopyKeyAdapter()); 98 } 99 100 // Call on EDT cancelCellEditing()101 public void cancelCellEditing() { 102 TableCellEditor tce = getCellEditor(); 103 if (tce != null) { 104 tce.cancelCellEditing(); 105 } 106 } 107 108 // Call on EDT stopCellEditing()109 public void stopCellEditing() { 110 TableCellEditor tce = getCellEditor(); 111 if (tce != null) { 112 tce.stopCellEditing(); 113 } 114 } 115 116 // Call on EDT 117 @Override isCellEditable(int row, int col)118 public boolean isCellEditable(int row, int col) { 119 UserDataCell cell = getUserDataCell(row, col); 120 if (cell != null) { 121 return cell.isMaximized(); 122 } 123 return true; 124 } 125 126 // Call on EDT 127 @Override setValueAt(Object value, int row, int column)128 public void setValueAt(Object value, int row, int column) { 129 } 130 131 // Call on EDT 132 @Override prepareRenderer( TableCellRenderer renderer, int row, int column)133 public synchronized Component prepareRenderer( 134 TableCellRenderer renderer, int row, int column) { 135 //In case we have a repaint thread that is in the process of 136 //repainting an obsolete table, just ignore the call. 137 //It can happen when MBean selection is switched at a very quick rate 138 if (row >= getRowCount()) { 139 return null; 140 } 141 142 Component comp = super.prepareRenderer(renderer, row, column); 143 144 if (normalFont == null) { 145 normalFont = comp.getFont(); 146 boldFont = normalFont.deriveFont(Font.BOLD); 147 } 148 UserDataCell cell = getUserDataCell(row, 2); 149 if (column == 2 && cell != null) { 150 comp.setFont(boldFont); 151 int size = cell.getHeight(); 152 if (size > 0) { 153 if (getRowHeight(row) != size) { 154 setRowHeight(row, size); 155 } 156 } 157 } else { 158 comp.setFont(normalFont); 159 } 160 161 return comp; 162 } 163 164 // Call on EDT 165 @Override getCellRenderer(int row, int column)166 public synchronized TableCellRenderer getCellRenderer(int row, int column) { 167 //In case we have a repaint thread that is in the process of 168 //repainting an obsolete table, just ignore the call. 169 //It can happen when MBean selection is switched at a very quick rate 170 if (row >= getRowCount()) { 171 return null; 172 } 173 174 DefaultTableCellRenderer renderer; 175 String toolTip = null; 176 UserDataCell cell = getUserDataCell(row, column); 177 if (cell != null && cell.isInited()) { 178 renderer = (DefaultTableCellRenderer) cell.getRenderer(); 179 } else { 180 renderer = 181 (DefaultTableCellRenderer) super.getCellRenderer(row, column); 182 } 183 184 if (cell != null) { 185 toolTip = Messages.DOUBLE_CLICK_TO_EXPAND_FORWARD_SLASH_COLLAPSE+ 186 ". " + cell.toString(); 187 } else { 188 Object val = 189 ((DefaultTableModel) getModel()).getValueAt(row, column); 190 if (val != null) { 191 toolTip = val.toString(); 192 } 193 } 194 195 renderer.setToolTipText(toolTip); 196 197 return renderer; 198 } 199 200 // Call on EDT getUserDataCell(int row, int column)201 private UserDataCell getUserDataCell(int row, int column) { 202 Object obj = ((DefaultTableModel) getModel()).getValueAt(row, column); 203 if (obj instanceof UserDataCell) { 204 return (UserDataCell) obj; 205 } 206 return null; 207 } 208 dispose()209 synchronized void dispose() { 210 listeners.clear(); 211 } 212 getReceivedNotifications(XMBean mbean)213 public long getReceivedNotifications(XMBean mbean) { 214 XMBeanNotificationsListener listener = 215 listeners.get(mbean.getObjectName()); 216 if (listener == null) { 217 return 0; 218 } else { 219 return listener.getReceivedNotifications(); 220 } 221 } 222 clearCurrentNotifications()223 public synchronized boolean clearCurrentNotifications() { 224 emptyTable(); 225 if (currentListener != null) { 226 currentListener.clear(); 227 return true; 228 } else { 229 return false; 230 } 231 } 232 unregisterListener(DefaultMutableTreeNode node)233 public synchronized boolean unregisterListener(DefaultMutableTreeNode node) { 234 XMBean mbean = (XMBean) ((XNodeInfo) node.getUserObject()).getData(); 235 return unregister(mbean.getObjectName()); 236 } 237 registerListener(DefaultMutableTreeNode node)238 public synchronized void registerListener(DefaultMutableTreeNode node) 239 throws InstanceNotFoundException, IOException { 240 XMBean mbean = (XMBean) ((XNodeInfo) node.getUserObject()).getData(); 241 if (!subscribed) { 242 try { 243 mbean.getMBeanServerConnection().addNotificationListener( 244 MBeanServerDelegate.DELEGATE_NAME, this, null, null); 245 subscribed = true; 246 } catch (Exception e) { 247 if (JConsole.isDebug()) { 248 System.err.println("Error adding listener for delegate:"); 249 e.printStackTrace(); 250 } 251 } 252 } 253 XMBeanNotificationsListener listener = 254 listeners.get(mbean.getObjectName()); 255 if (listener == null) { 256 listener = new XMBeanNotificationsListener( 257 this, mbean, node, columnNames); 258 listeners.put(mbean.getObjectName(), listener); 259 } else { 260 if (!listener.isRegistered()) { 261 emptyTable(); 262 listener.register(node); 263 } 264 } 265 enabled = true; 266 currentListener = listener; 267 } 268 handleNotification( Notification notif, Object handback)269 public synchronized void handleNotification( 270 Notification notif, Object handback) { 271 try { 272 if (notif instanceof MBeanServerNotification) { 273 ObjectName mbean = 274 ((MBeanServerNotification) notif).getMBeanName(); 275 if (notif.getType().indexOf("JMX.mbean.unregistered") >= 0) { 276 unregister(mbean); 277 } 278 } 279 } catch (Exception e) { 280 if (JConsole.isDebug()) { 281 System.err.println("Error unregistering notification:"); 282 e.printStackTrace(); 283 } 284 } 285 } 286 disableNotifications()287 public synchronized void disableNotifications() { 288 emptyTable(); 289 currentListener = null; 290 enabled = false; 291 } 292 unregister(ObjectName mbean)293 private synchronized boolean unregister(ObjectName mbean) { 294 XMBeanNotificationsListener listener = listeners.get(mbean); 295 if (listener != null && listener.isRegistered()) { 296 listener.unregister(); 297 return true; 298 } else { 299 return false; 300 } 301 } 302 addNotificationsListener(NotificationListener nl)303 public void addNotificationsListener(NotificationListener nl) { 304 notificationListenersList.add(nl); 305 } 306 removeNotificationsListener(NotificationListener nl)307 public void removeNotificationsListener(NotificationListener nl) { 308 notificationListenersList.remove(nl); 309 } 310 311 // Call on EDT fireNotificationReceived( XMBeanNotificationsListener listener, XMBean mbean, DefaultMutableTreeNode node, Object[] rowData, long received)312 void fireNotificationReceived( 313 XMBeanNotificationsListener listener, XMBean mbean, 314 DefaultMutableTreeNode node, Object[] rowData, long received) { 315 if (enabled) { 316 DefaultTableModel tableModel = (DefaultTableModel) getModel(); 317 if (listener == currentListener) { 318 tableModel.insertRow(0, rowData); 319 repaint(); 320 } 321 } 322 Notification notif = 323 new Notification(NOTIFICATION_RECEIVED_EVENT, this, 0); 324 notif.setUserData(received); 325 for (NotificationListener nl : notificationListenersList) { 326 nl.handleNotification(notif, node); 327 } 328 } 329 330 // Call on EDT updateModel(List<Object[]> data)331 private void updateModel(List<Object[]> data) { 332 emptyTable(); 333 DefaultTableModel tableModel = (DefaultTableModel) getModel(); 334 for (Object[] rowData : data) { 335 tableModel.addRow(rowData); 336 } 337 } 338 isListenerRegistered(XMBean mbean)339 public synchronized boolean isListenerRegistered(XMBean mbean) { 340 XMBeanNotificationsListener listener = 341 listeners.get(mbean.getObjectName()); 342 if (listener == null) { 343 return false; 344 } 345 return listener.isRegistered(); 346 } 347 348 // Call on EDT loadNotifications(XMBean mbean)349 public synchronized void loadNotifications(XMBean mbean) { 350 XMBeanNotificationsListener listener = 351 listeners.get(mbean.getObjectName()); 352 emptyTable(); 353 if (listener != null) { 354 enabled = true; 355 List<Object[]> data = listener.getData(); 356 updateModel(data); 357 currentListener = listener; 358 validate(); 359 repaint(); 360 } else { 361 enabled = false; 362 } 363 } 364 365 // Call on EDT setColumnEditors()366 private void setColumnEditors() { 367 TableColumnModel tcm = getColumnModel(); 368 for (int i = 0; i < columnNames.length; i++) { 369 TableColumn tc = tcm.getColumn(i); 370 if (i == 2) { 371 tc.setCellEditor(userDataEditor); 372 } else { 373 tc.setCellEditor(editor); 374 } 375 } 376 } 377 378 // Call on EDT isTableEditable()379 public boolean isTableEditable() { 380 return true; 381 } 382 383 // Call on EDT emptyTable()384 public synchronized void emptyTable() { 385 DefaultTableModel model = (DefaultTableModel) getModel(); 386 //invalidate(); 387 while (model.getRowCount() > 0) { 388 model.removeRow(0); 389 } 390 validate(); 391 } 392 393 // Call on EDT updateUserDataCell(int row, int col)394 synchronized void updateUserDataCell(int row, int col) { 395 Object obj = getModel().getValueAt(row, 2); 396 if (obj instanceof UserDataCell) { 397 UserDataCell cell = (UserDataCell) obj; 398 if (!cell.isInited()) { 399 if (rowMinHeight == -1) { 400 rowMinHeight = getRowHeight(row); 401 } 402 cell.init(super.getCellRenderer(row, col), rowMinHeight); 403 } 404 405 cell.switchState(); 406 setRowHeight(row, cell.getHeight()); 407 408 if (!cell.isMaximized()) { 409 cancelCellEditing(); 410 //Back to simple editor. 411 editCellAt(row, 2); 412 } 413 414 invalidate(); 415 repaint(); 416 } 417 } 418 419 class UserDataCellRenderer extends DefaultTableCellRenderer { 420 421 Component comp; 422 UserDataCellRenderer(Component comp)423 UserDataCellRenderer(Component comp) { 424 this.comp = comp; 425 Dimension d = comp.getPreferredSize(); 426 if (d.getHeight() > 200) { 427 comp.setPreferredSize(new Dimension((int) d.getWidth(), 200)); 428 } 429 } 430 431 @Override getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)432 public Component getTableCellRendererComponent( 433 JTable table, 434 Object value, 435 boolean isSelected, 436 boolean hasFocus, 437 int row, 438 int column) { 439 return comp; 440 } 441 getComponent()442 public Component getComponent() { 443 return comp; 444 } 445 } 446 447 class UserDataCell { 448 449 TableCellRenderer minRenderer; 450 UserDataCellRenderer maxRenderer; 451 int minHeight; 452 boolean minimized = true; 453 boolean init = false; 454 Object userData; 455 UserDataCell(Object userData, Component max)456 UserDataCell(Object userData, Component max) { 457 this.userData = userData; 458 this.maxRenderer = new UserDataCellRenderer(max); 459 460 } 461 462 @Override toString()463 public String toString() { 464 if (userData == null) { 465 return null; 466 } 467 if (userData.getClass().isArray()) { 468 String name = 469 Utils.getArrayClassName(userData.getClass().getName()); 470 int length = Array.getLength(userData); 471 return name + "[" + length + "]"; 472 } 473 474 if (userData instanceof CompositeData || 475 userData instanceof TabularData) { 476 return userData.getClass().getName(); 477 } 478 479 return userData.toString(); 480 } 481 isInited()482 boolean isInited() { 483 return init; 484 } 485 init(TableCellRenderer minRenderer, int minHeight)486 void init(TableCellRenderer minRenderer, int minHeight) { 487 this.minRenderer = minRenderer; 488 this.minHeight = minHeight; 489 init = true; 490 } 491 switchState()492 void switchState() { 493 minimized = !minimized; 494 } 495 isMaximized()496 boolean isMaximized() { 497 return !minimized; 498 } 499 minimize()500 void minimize() { 501 minimized = true; 502 } 503 maximize()504 void maximize() { 505 minimized = false; 506 } 507 getHeight()508 int getHeight() { 509 if (minimized) { 510 return minHeight; 511 } else { 512 return (int) maxRenderer.getComponent(). 513 getPreferredSize().getHeight(); 514 } 515 } 516 getRenderer()517 TableCellRenderer getRenderer() { 518 if (minimized) { 519 return minRenderer; 520 } else { 521 return maxRenderer; 522 } 523 } 524 } 525 526 class NotifMouseListener extends MouseAdapter { 527 528 @Override mousePressed(MouseEvent e)529 public void mousePressed(MouseEvent e) { 530 if (e.getButton() == MouseEvent.BUTTON1) { 531 if (e.getClickCount() >= 2) { 532 int row = XMBeanNotifications.this.getSelectedRow(); 533 int col = XMBeanNotifications.this.getSelectedColumn(); 534 if (col != 2) { 535 return; 536 } 537 if (col == -1 || row == -1) { 538 return; 539 } 540 541 XMBeanNotifications.this.updateUserDataCell(row, col); 542 } 543 } 544 } 545 } 546 547 class UserDataCellEditor extends XTextFieldEditor { 548 // implements javax.swing.table.TableCellEditor 549 @Override getTableCellEditorComponent( JTable table, Object value, boolean isSelected, int row, int column)550 public Component getTableCellEditorComponent( 551 JTable table, 552 Object value, 553 boolean isSelected, 554 int row, 555 int column) { 556 Object val = value; 557 if (column == 2) { 558 Object obj = getModel().getValueAt(row, column); 559 if (obj instanceof UserDataCell) { 560 UserDataCell cell = (UserDataCell) obj; 561 if (cell.getRenderer() instanceof UserDataCellRenderer) { 562 UserDataCellRenderer zr = 563 (UserDataCellRenderer) cell.getRenderer(); 564 return zr.getComponent(); 565 } 566 } else { 567 Component comp = super.getTableCellEditorComponent( 568 table, val, isSelected, row, column); 569 textField.setEditable(false); 570 return comp; 571 } 572 } 573 return super.getTableCellEditorComponent( 574 table, 575 val, 576 isSelected, 577 row, 578 column); 579 } 580 581 @Override stopCellEditing()582 public boolean stopCellEditing() { 583 int editingRow = getEditingRow(); 584 int editingColumn = getEditingColumn(); 585 if (editingColumn == 2) { 586 Object obj = getModel().getValueAt(editingRow, editingColumn); 587 if (obj instanceof UserDataCell) { 588 UserDataCell cell = (UserDataCell) obj; 589 if (cell.isMaximized()) { 590 cancelCellEditing(); 591 return true; 592 } 593 } 594 } 595 return super.stopCellEditing(); 596 } 597 } 598 599 class XMBeanNotificationsListener implements NotificationListener { 600 601 private XMBean xmbean; 602 private DefaultMutableTreeNode node; 603 private volatile long received; 604 private XMBeanNotifications notifications; 605 private volatile boolean unregistered; 606 private ArrayList<Object[]> data = new ArrayList<Object[]>(); 607 XMBeanNotificationsListener( XMBeanNotifications notifications, XMBean xmbean, DefaultMutableTreeNode node, String[] columnNames)608 public XMBeanNotificationsListener( 609 XMBeanNotifications notifications, 610 XMBean xmbean, 611 DefaultMutableTreeNode node, 612 String[] columnNames) { 613 this.notifications = notifications; 614 this.xmbean = xmbean; 615 this.node = node; 616 register(node); 617 } 618 getData()619 public synchronized List<Object[]> getData() { 620 return data; 621 } 622 clear()623 public synchronized void clear() { 624 data.clear(); 625 received = 0; 626 } 627 isRegistered()628 public synchronized boolean isRegistered() { 629 return !unregistered; 630 } 631 unregister()632 public synchronized void unregister() { 633 try { 634 xmbean.getMBeanServerConnection().removeNotificationListener( 635 xmbean.getObjectName(), this, null, null); 636 } catch (Exception e) { 637 if (JConsole.isDebug()) { 638 System.err.println("Error removing listener:"); 639 e.printStackTrace(); 640 } 641 } 642 unregistered = true; 643 } 644 getReceivedNotifications()645 public synchronized long getReceivedNotifications() { 646 return received; 647 } 648 register(DefaultMutableTreeNode node)649 public synchronized void register(DefaultMutableTreeNode node) { 650 clear(); 651 this.node = node; 652 try { 653 xmbean.getMBeanServerConnection().addNotificationListener( 654 xmbean.getObjectName(), this, null, null); 655 unregistered = false; 656 } catch (Exception e) { 657 if (JConsole.isDebug()) { 658 System.err.println("Error adding listener:"); 659 e.printStackTrace(); 660 } 661 } 662 } 663 handleNotification( final Notification n, Object hb)664 public synchronized void handleNotification( 665 final Notification n, Object hb) { 666 EventQueue.invokeLater(new Runnable() { 667 668 public void run() { 669 synchronized (XMBeanNotificationsListener.this) { 670 try { 671 if (unregistered) { 672 return; 673 } 674 Date receivedDate = new Date(n.getTimeStamp()); 675 String time = timeFormater.format(receivedDate); 676 677 Object userData = n.getUserData(); 678 Component comp = null; 679 UserDataCell cell = null; 680 if ((comp = XDataViewer.createNotificationViewer(userData)) != null) { 681 XDataViewer.registerForMouseEvent(comp, mouseListener); 682 cell = new UserDataCell(userData, comp); 683 } 684 685 Object[] rowData = { 686 time, 687 n.getType(), 688 (cell == null ? userData : cell), 689 n.getSequenceNumber(), 690 n.getMessage(), 691 n, 692 n.getSource() 693 }; 694 received++; 695 data.add(0, rowData); 696 697 notifications.fireNotificationReceived( 698 XMBeanNotificationsListener.this, 699 xmbean, node, rowData, received); 700 } catch (Exception e) { 701 if (JConsole.isDebug()) { 702 System.err.println("Error handling notification:"); 703 e.printStackTrace(); 704 } 705 } 706 } 707 } 708 }); 709 } 710 } 711 } 712