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