1 /* 2 * Copyright (c) 2004, 2008, 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.resources.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 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 101 public void cancelCellEditing() { 102 TableCellEditor tce = getCellEditor(); 103 if (tce != null) { 104 tce.cancelCellEditing(); 105 } 106 } 107 108 // Call on EDT 109 public void stopCellEditing() { 110 TableCellEditor tce = getCellEditor(); 111 if (tce != null) { 112 tce.stopCellEditing(); 113 } 114 } 115 116 // Call on EDT 117 @Override 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 128 public void setValueAt(Object value, int row, int column) { 129 } 130 131 // Call on EDT 132 @Override 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 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 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 209 synchronized void dispose() { 210 listeners.clear(); 211 } 212 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 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 233 public synchronized boolean unregisterListener(DefaultMutableTreeNode node) { 234 XMBean mbean = (XMBean) ((XNodeInfo) node.getUserObject()).getData(); 235 return unregister(mbean.getObjectName()); 236 } 237 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 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 287 public synchronized void disableNotifications() { 288 emptyTable(); 289 currentListener = null; 290 enabled = false; 291 } 292 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 303 public void addNotificationsListener(NotificationListener nl) { 304 notificationListenersList.add(nl); 305 } 306 307 public void removeNotificationsListener(NotificationListener nl) { 308 notificationListenersList.remove(nl); 309 } 310 311 // Call on EDT 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 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 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 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 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 379 public boolean isTableEditable() { 380 return true; 381 } 382 383 // Call on EDT 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 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 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 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 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 456 UserDataCell(Object userData, Component max) { 457 this.userData = userData; 458 this.maxRenderer = new UserDataCellRenderer(max); 459 460 } 461 462 @Override 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 482 boolean isInited() { 483 return init; 484 } 485 486 void init(TableCellRenderer minRenderer, int minHeight) { 487 this.minRenderer = minRenderer; 488 this.minHeight = minHeight; 489 init = true; 490 } 491 492 void switchState() { 493 minimized = !minimized; 494 } 495 496 boolean isMaximized() { 497 return !minimized; 498 } 499 500 void minimize() { 501 minimized = true; 502 } 503 504 void maximize() { 505 minimized = false; 506 } 507 508 int getHeight() { 509 if (minimized) { 510 return minHeight; 511 } else { 512 return (int) maxRenderer.getComponent(). 513 getPreferredSize().getHeight(); 514 } 515 } 516 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 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 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 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 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 619 public synchronized List<Object[]> getData() { 620 return data; 621 } 622 623 public synchronized void clear() { 624 data.clear(); 625 received = 0; 626 } 627 628 public synchronized boolean isRegistered() { 629 return !unregistered; 630 } 631 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 645 public synchronized long getReceivedNotifications() { 646 return received; 647 } 648 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 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 }