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 29 import java.awt.Component; 30 import java.awt.EventQueue; 31 import java.awt.Dimension; 32 import java.awt.event.MouseAdapter; 33 import java.awt.event.MouseEvent; 34 import java.io.IOException; 35 36 import java.lang.reflect.Array; 37 38 import java.util.EventObject; 39 import java.util.HashMap; 40 import java.util.WeakHashMap; 41 42 import java.util.concurrent.ExecutionException; 43 import java.lang.System.Logger; 44 import java.lang.System.Logger.Level; 45 import javax.management.JMException; 46 import javax.management.MBeanInfo; 47 import javax.management.MBeanAttributeInfo; 48 import javax.management.AttributeList; 49 import javax.management.Attribute; 50 import javax.management.openmbean.CompositeData; 51 import javax.management.openmbean.TabularData; 52 53 import javax.swing.JComponent; 54 import javax.swing.JOptionPane; 55 import javax.swing.JTable; 56 import javax.swing.JTextField; 57 import javax.swing.SwingWorker; 58 import javax.swing.event.ChangeEvent; 59 import javax.swing.event.TableModelEvent; 60 import javax.swing.event.TableModelListener; 61 import javax.swing.table.DefaultTableCellRenderer; 62 import javax.swing.table.DefaultTableModel; 63 import javax.swing.table.TableCellEditor; 64 import javax.swing.table.TableCellRenderer; 65 import javax.swing.table.TableColumn; 66 import javax.swing.table.TableColumnModel; 67 import javax.swing.table.TableModel; 68 69 import sun.tools.jconsole.MBeansTab; 70 import sun.tools.jconsole.JConsole; 71 import sun.tools.jconsole.Messages; 72 import sun.tools.jconsole.ProxyClient.SnapshotMBeanServerConnection; 73 74 /*IMPORTANT : 75 There is a deadlock issue there if we don't synchronize well loadAttributes, 76 refresh attributes and empty table methods since a UI thread can call 77 loadAttributes and at the same time a JMX notification can raise an 78 emptyTable. Since there are synchronization in the JMX world it's 79 COMPULSORY to not call the JMX world in synchronized blocks */ 80 @SuppressWarnings("serial") 81 public class XMBeanAttributes extends XTable { 82 83 final Logger LOGGER = 84 System.getLogger(XMBeanAttributes.class.getPackage().getName()); 85 86 private final static String[] columnNames = 87 {Messages.NAME, 88 Messages.VALUE}; 89 90 private XMBean mbean; 91 private MBeanInfo mbeanInfo; 92 private MBeanAttributeInfo[] attributesInfo; 93 private HashMap<String, Object> attributes; 94 private HashMap<String, Object> unavailableAttributes; 95 private HashMap<String, Object> viewableAttributes; 96 private WeakHashMap<XMBean, HashMap<String, ZoomedCell>> viewersCache = 97 new WeakHashMap<XMBean, HashMap<String, ZoomedCell>>(); 98 private final TableModelListener attributesListener; 99 private MBeansTab mbeansTab; 100 private TableCellEditor valueCellEditor = new ValueCellEditor(); 101 private int rowMinHeight = -1; 102 private AttributesMouseListener mouseListener = new AttributesMouseListener(); 103 104 private static TableCellEditor editor = 105 new Utils.ReadOnlyTableCellEditor(new JTextField()); 106 XMBeanAttributes(MBeansTab mbeansTab)107 public XMBeanAttributes(MBeansTab mbeansTab) { 108 super(); 109 this.mbeansTab = mbeansTab; 110 ((DefaultTableModel)getModel()).setColumnIdentifiers(columnNames); 111 attributesListener = new AttributesListener(this); 112 getModel().addTableModelListener(attributesListener); 113 getColumnModel().getColumn(NAME_COLUMN).setPreferredWidth(40); 114 115 addMouseListener(mouseListener); 116 getTableHeader().setReorderingAllowed(false); 117 setColumnEditors(); 118 addKeyListener(new Utils.CopyKeyAdapter()); 119 } 120 121 @Override prepareRenderer(TableCellRenderer renderer, int row, int column)122 public synchronized Component prepareRenderer(TableCellRenderer renderer, 123 int row, int column) { 124 //In case we have a repaint thread that is in the process of 125 //repainting an obsolete table, just ignore the call. 126 //It can happen when MBean selection is switched at a very quick rate 127 if(row >= getRowCount()) 128 return null; 129 else 130 return super.prepareRenderer(renderer, row, column); 131 } 132 updateRowHeight(Object obj, int row)133 void updateRowHeight(Object obj, int row) { 134 ZoomedCell cell = null; 135 if(obj instanceof ZoomedCell) { 136 cell = (ZoomedCell) obj; 137 if(cell.isInited()) 138 setRowHeight(row, cell.getHeight()); 139 else 140 if(rowMinHeight != - 1) 141 setRowHeight(row, rowMinHeight); 142 } else 143 if(rowMinHeight != - 1) 144 setRowHeight(row, rowMinHeight); 145 } 146 147 @Override getCellRenderer(int row, int column)148 public synchronized TableCellRenderer getCellRenderer(int row, 149 int column) { 150 //In case we have a repaint thread that is in the process of 151 //repainting an obsolete table, just ignore the call. 152 //It can happen when MBean selection is switched at a very quick rate 153 if (row >= getRowCount()) { 154 return null; 155 } else { 156 if (column == VALUE_COLUMN) { 157 Object obj = getModel().getValueAt(row, column); 158 if (obj instanceof ZoomedCell) { 159 ZoomedCell cell = (ZoomedCell) obj; 160 if (cell.isInited()) { 161 DefaultTableCellRenderer renderer = 162 (DefaultTableCellRenderer) cell.getRenderer(); 163 renderer.setToolTipText(getToolTip(row,column)); 164 return renderer; 165 } 166 } 167 } 168 DefaultTableCellRenderer renderer = (DefaultTableCellRenderer) 169 super.getCellRenderer(row, column); 170 if (!isCellError(row, column)) { 171 if (!(isColumnEditable(column) && isWritable(row) && 172 Utils.isEditableType(getClassName(row)))) { 173 renderer.setForeground(getDefaultColor()); 174 } 175 } 176 return renderer; 177 } 178 } 179 setColumnEditors()180 private void setColumnEditors() { 181 TableColumnModel tcm = getColumnModel(); 182 for (int i = 0; i < columnNames.length; i++) { 183 TableColumn tc = tcm.getColumn(i); 184 if (isColumnEditable(i)) { 185 tc.setCellEditor(valueCellEditor); 186 } else { 187 tc.setCellEditor(editor); 188 } 189 } 190 } 191 cancelCellEditing()192 public void cancelCellEditing() { 193 if (LOGGER.isLoggable(Level.TRACE)) { 194 LOGGER.log(Level.TRACE, "Cancel Editing Row: "+getEditingRow()); 195 } 196 final TableCellEditor tableCellEditor = getCellEditor(); 197 if (tableCellEditor != null) { 198 tableCellEditor.cancelCellEditing(); 199 } 200 } 201 stopCellEditing()202 public void stopCellEditing() { 203 if (LOGGER.isLoggable(Level.TRACE)) { 204 LOGGER.log(Level.TRACE, "Stop Editing Row: "+getEditingRow()); 205 } 206 final TableCellEditor tableCellEditor = getCellEditor(); 207 if (tableCellEditor != null) { 208 tableCellEditor.stopCellEditing(); 209 } 210 } 211 212 @Override editCellAt(final int row, final int column, EventObject e)213 public final boolean editCellAt(final int row, final int column, EventObject e) { 214 if (LOGGER.isLoggable(Level.TRACE)) { 215 LOGGER.log(Level.TRACE, "editCellAt(row="+row+", col="+column+ 216 ", e="+e+")"); 217 } 218 if (JConsole.isDebug()) { 219 System.err.println("edit: "+getValueName(row)+"="+getValue(row)); 220 } 221 boolean retVal = super.editCellAt(row, column, e); 222 if (retVal) { 223 final TableCellEditor tableCellEditor = 224 getColumnModel().getColumn(column).getCellEditor(); 225 if (tableCellEditor == valueCellEditor) { 226 ((JComponent) tableCellEditor).requestFocus(); 227 } 228 } 229 return retVal; 230 } 231 232 @Override isCellEditable(int row, int col)233 public boolean isCellEditable(int row, int col) { 234 // All the cells in non-editable columns are editable 235 if (!isColumnEditable(col)) { 236 return true; 237 } 238 // Maximized zoomed cells are editable 239 Object obj = getModel().getValueAt(row, col); 240 if (obj instanceof ZoomedCell) { 241 ZoomedCell cell = (ZoomedCell) obj; 242 return cell.isMaximized(); 243 } 244 return true; 245 } 246 247 @Override setValueAt(Object value, int row, int column)248 public void setValueAt(Object value, int row, int column) { 249 if (!isCellError(row, column) && isColumnEditable(column) && 250 isWritable(row) && Utils.isEditableType(getClassName(row))) { 251 if (JConsole.isDebug()) { 252 System.err.println("validating [row="+row+", column="+column+ 253 "]: "+getValueName(row)+"="+value); 254 } 255 super.setValueAt(value, row, column); 256 } 257 } 258 259 //Table methods 260 isTableEditable()261 public boolean isTableEditable() { 262 return true; 263 } 264 setTableValue(Object value, int row)265 public void setTableValue(Object value, int row) { 266 } 267 isColumnEditable(int column)268 public boolean isColumnEditable(int column) { 269 if (column < getColumnCount()) { 270 return getColumnName(column).equals(Messages.VALUE); 271 } 272 else { 273 return false; 274 } 275 } 276 getClassName(int row)277 public String getClassName(int row) { 278 int index = convertRowToIndex(row); 279 if (index != -1) { 280 return attributesInfo[index].getType(); 281 } 282 else { 283 return null; 284 } 285 } 286 287 getValueName(int row)288 public String getValueName(int row) { 289 int index = convertRowToIndex(row); 290 if (index != -1) { 291 return attributesInfo[index].getName(); 292 } 293 else { 294 return null; 295 } 296 } 297 getValue(int row)298 public Object getValue(int row) { 299 final Object val = ((DefaultTableModel) getModel()) 300 .getValueAt(row, VALUE_COLUMN); 301 return val; 302 } 303 304 //tool tip only for editable column 305 @Override getToolTip(int row, int column)306 public String getToolTip(int row, int column) { 307 if (isCellError(row, column)) { 308 return (String) unavailableAttributes.get(getValueName(row)); 309 } 310 if (isColumnEditable(column)) { 311 Object value = getValue(row); 312 String tip = null; 313 if (value != null) { 314 tip = value.toString(); 315 if(isAttributeViewable(row, VALUE_COLUMN)) 316 tip = Messages.DOUBLE_CLICK_TO_EXPAND_FORWARD_SLASH_COLLAPSE+ 317 ". " + tip; 318 } 319 320 return tip; 321 } 322 323 if(column == NAME_COLUMN) { 324 int index = convertRowToIndex(row); 325 if (index != -1) { 326 return attributesInfo[index].getDescription(); 327 } 328 } 329 return null; 330 } 331 isWritable(int row)332 public synchronized boolean isWritable(int row) { 333 int index = convertRowToIndex(row); 334 if (index != -1) { 335 return (attributesInfo[index].isWritable()); 336 } 337 else { 338 return false; 339 } 340 } 341 342 /** 343 * Override JTable method in order to make any call to this method 344 * atomic with TableModel elements. 345 */ 346 @Override getRowCount()347 public synchronized int getRowCount() { 348 return super.getRowCount(); 349 } 350 isReadable(int row)351 public synchronized boolean isReadable(int row) { 352 int index = convertRowToIndex(row); 353 if (index != -1) { 354 return (attributesInfo[index].isReadable()); 355 } 356 else { 357 return false; 358 } 359 } 360 isCellError(int row, int col)361 public synchronized boolean isCellError(int row, int col) { 362 return (isColumnEditable(col) && 363 (unavailableAttributes.containsKey(getValueName(row)))); 364 } 365 isAttributeViewable(int row, int col)366 public synchronized boolean isAttributeViewable(int row, int col) { 367 boolean isViewable = false; 368 if(col == VALUE_COLUMN) { 369 Object obj = getModel().getValueAt(row, col); 370 if(obj instanceof ZoomedCell) 371 isViewable = true; 372 } 373 374 return isViewable; 375 } 376 377 // Call this in EDT loadAttributes(final XMBean mbean, final MBeanInfo mbeanInfo)378 public void loadAttributes(final XMBean mbean, final MBeanInfo mbeanInfo) { 379 380 final SwingWorker<Runnable,Void> load = 381 new SwingWorker<Runnable,Void>() { 382 @Override 383 protected Runnable doInBackground() throws Exception { 384 return doLoadAttributes(mbean,mbeanInfo); 385 } 386 387 @Override 388 protected void done() { 389 try { 390 final Runnable updateUI = get(); 391 if (updateUI != null) updateUI.run(); 392 } catch (RuntimeException x) { 393 throw x; 394 } catch (ExecutionException x) { 395 if(JConsole.isDebug()) { 396 System.err.println( 397 "Exception raised while loading attributes: " 398 +x.getCause()); 399 x.printStackTrace(); 400 } 401 } catch (InterruptedException x) { 402 if(JConsole.isDebug()) { 403 System.err.println( 404 "Interrupted while loading attributes: "+x); 405 x.printStackTrace(); 406 } 407 } 408 } 409 410 }; 411 mbeansTab.workerAdd(load); 412 } 413 414 // Don't call this in EDT, but execute returned Runnable inside 415 // EDT - typically in the done() method of a SwingWorker 416 // This method can return null. doLoadAttributes(final XMBean mbean, MBeanInfo infoOrNull)417 private Runnable doLoadAttributes(final XMBean mbean, MBeanInfo infoOrNull) 418 throws JMException, IOException { 419 // To avoid deadlock with events coming from the JMX side, 420 // we retrieve all JMX stuff in a non synchronized block. 421 422 if(mbean == null) return null; 423 final MBeanInfo curMBeanInfo = 424 (infoOrNull==null)?mbean.getMBeanInfo():infoOrNull; 425 426 final MBeanAttributeInfo[] attrsInfo = curMBeanInfo.getAttributes(); 427 final HashMap<String, Object> attrs = 428 new HashMap<String, Object>(attrsInfo.length); 429 final HashMap<String, Object> unavailableAttrs = 430 new HashMap<String, Object>(attrsInfo.length); 431 final HashMap<String, Object> viewableAttrs = 432 new HashMap<String, Object>(attrsInfo.length); 433 AttributeList list = null; 434 435 try { 436 list = mbean.getAttributes(attrsInfo); 437 }catch(Exception e) { 438 if (JConsole.isDebug()) { 439 System.err.println("Error calling getAttributes() on MBean \"" + 440 mbean.getObjectName() + "\". JConsole will " + 441 "try to get them individually calling " + 442 "getAttribute() instead. Exception:"); 443 e.printStackTrace(System.err); 444 } 445 list = new AttributeList(); 446 //Can't load all attributes, do it one after each other. 447 for(int i = 0; i < attrsInfo.length; i++) { 448 String name = null; 449 try { 450 name = attrsInfo[i].getName(); 451 Object value = 452 mbean.getMBeanServerConnection(). 453 getAttribute(mbean.getObjectName(), name); 454 list.add(new Attribute(name, value)); 455 }catch(Exception ex) { 456 if(attrsInfo[i].isReadable()) { 457 unavailableAttrs.put(name, 458 Utils.getActualException(ex).toString()); 459 } 460 } 461 } 462 } 463 try { 464 int att_length = list.size(); 465 for (int i=0;i<att_length;i++) { 466 Attribute attribute = (Attribute) list.get(i); 467 if(isViewable(attribute)) { 468 viewableAttrs.put(attribute.getName(), 469 attribute.getValue()); 470 } 471 else 472 attrs.put(attribute.getName(),attribute.getValue()); 473 474 } 475 // if not all attributes are accessible, 476 // check them one after the other. 477 if (att_length < attrsInfo.length) { 478 for (int i=0;i<attrsInfo.length;i++) { 479 MBeanAttributeInfo attributeInfo = attrsInfo[i]; 480 if (!attrs.containsKey(attributeInfo.getName()) && 481 !viewableAttrs.containsKey(attributeInfo. 482 getName()) && 483 !unavailableAttrs.containsKey(attributeInfo. 484 getName())) { 485 if (attributeInfo.isReadable()) { 486 // getAttributes didn't help resolving the 487 // exception. 488 // We must call it again to understand what 489 // went wrong. 490 try { 491 Object v = 492 mbean.getMBeanServerConnection().getAttribute( 493 mbean.getObjectName(), attributeInfo.getName()); 494 //What happens if now it is ok? 495 // Be pragmatic, add it to readable... 496 attrs.put(attributeInfo.getName(), 497 v); 498 }catch(Exception e) { 499 //Put the exception that will be displayed 500 // in tooltip 501 unavailableAttrs.put(attributeInfo.getName(), 502 Utils.getActualException(e).toString()); 503 } 504 } 505 } 506 } 507 } 508 } 509 catch(Exception e) { 510 //sets all attributes unavailable except the writable ones 511 for (int i=0;i<attrsInfo.length;i++) { 512 MBeanAttributeInfo attributeInfo = attrsInfo[i]; 513 if (attributeInfo.isReadable()) { 514 unavailableAttrs.put(attributeInfo.getName(), 515 Utils.getActualException(e). 516 toString()); 517 } 518 } 519 } 520 //end of retrieval 521 522 //one update at a time 523 return new Runnable() { 524 public void run() { 525 synchronized (XMBeanAttributes.this) { 526 XMBeanAttributes.this.mbean = mbean; 527 XMBeanAttributes.this.mbeanInfo = curMBeanInfo; 528 XMBeanAttributes.this.attributesInfo = attrsInfo; 529 XMBeanAttributes.this.attributes = attrs; 530 XMBeanAttributes.this.unavailableAttributes = unavailableAttrs; 531 XMBeanAttributes.this.viewableAttributes = viewableAttrs; 532 533 DefaultTableModel tableModel = 534 (DefaultTableModel) getModel(); 535 536 // add attribute information 537 emptyTable(tableModel); 538 539 addTableData(tableModel, 540 mbean, 541 attrsInfo, 542 attrs, 543 unavailableAttrs, 544 viewableAttrs); 545 546 // update the model with the new data 547 tableModel.newDataAvailable(new TableModelEvent(tableModel)); 548 // re-register for change events 549 tableModel.addTableModelListener(attributesListener); 550 } 551 } 552 }; 553 } 554 555 void collapse(String attributeName, final Component c) { 556 final int row = getSelectedRow(); 557 Object obj = getModel().getValueAt(row, VALUE_COLUMN); 558 if(obj instanceof ZoomedCell) { 559 cancelCellEditing(); 560 ZoomedCell cell = (ZoomedCell) obj; 561 cell.reset(); 562 setRowHeight(row, 563 cell.getHeight()); 564 editCellAt(row, 565 VALUE_COLUMN); 566 invalidate(); 567 repaint(); 568 } 569 } 570 571 ZoomedCell updateZoomedCell(int row, 572 int col) { 573 Object obj = getModel().getValueAt(row, VALUE_COLUMN); 574 ZoomedCell cell = null; 575 if(obj instanceof ZoomedCell) { 576 cell = (ZoomedCell) obj; 577 if(!cell.isInited()) { 578 Object elem = cell.getValue(); 579 String attributeName = 580 (String) getModel().getValueAt(row, 581 NAME_COLUMN); 582 Component comp = mbeansTab.getDataViewer(). 583 createAttributeViewer(elem, mbean, attributeName, this); 584 if(comp != null){ 585 if(rowMinHeight == -1) 586 rowMinHeight = getRowHeight(row); 587 588 cell.init(super.getCellRenderer(row, col), 589 comp, 590 rowMinHeight); 591 592 XDataViewer.registerForMouseEvent( 593 comp, mouseListener); 594 } else 595 return cell; 596 } 597 598 cell.switchState(); 599 setRowHeight(row, 600 cell.getHeight()); 601 602 if(!cell.isMaximized()) { 603 cancelCellEditing(); 604 //Back to simple editor. 605 editCellAt(row, 606 VALUE_COLUMN); 607 } 608 609 invalidate(); 610 repaint(); 611 } 612 return cell; 613 } 614 615 // This is called by XSheet when the "refresh" button is pressed. 616 // In this case we will commit any pending attribute values by 617 // calling 'stopCellEditing'. 618 // 619 public void refreshAttributes() { 620 refreshAttributes(true); 621 } 622 623 // refreshAttributes(false) is called by tableChanged(). 624 // in this case we must not call stopCellEditing, because it's already 625 // been called - e.g. 626 // lostFocus/mousePressed -> stopCellEditing -> setValueAt -> tableChanged 627 // -> refreshAttributes(false) 628 // 629 // Can be called in EDT - as long as the implementation of 630 // mbeansTab.getCachedMBeanServerConnection() and mbsc.flush() doesn't 631 // change 632 // 633 private void refreshAttributes(final boolean stopCellEditing) { 634 SwingWorker<Void,Void> sw = new SwingWorker<Void,Void>() { 635 636 @Override 637 protected Void doInBackground() throws Exception { 638 SnapshotMBeanServerConnection mbsc = 639 mbeansTab.getSnapshotMBeanServerConnection(); 640 mbsc.flush(); 641 return null; 642 } 643 644 @Override 645 protected void done() { 646 try { 647 get(); 648 if (stopCellEditing) stopCellEditing(); 649 loadAttributes(mbean, mbeanInfo); 650 } catch (Exception x) { 651 if (JConsole.isDebug()) { 652 x.printStackTrace(); 653 } 654 } 655 } 656 }; 657 mbeansTab.workerAdd(sw); 658 } 659 // We need to call stop editing here - otherwise edits are lost 660 // when resizing the table. 661 // 662 @Override 663 public void columnMarginChanged(ChangeEvent e) { 664 if (isEditing()) stopCellEditing(); 665 super.columnMarginChanged(e); 666 } 667 668 // We need to call stop editing here - otherwise the edited value 669 // is transferred to the wrong row... 670 // 671 @Override 672 void sortRequested(int column) { 673 if (isEditing()) stopCellEditing(); 674 super.sortRequested(column); 675 } 676 677 678 @Override 679 public synchronized void emptyTable() { 680 emptyTable((DefaultTableModel)getModel()); 681 } 682 683 // Call this in synchronized block. 684 private void emptyTable(DefaultTableModel model) { 685 model.removeTableModelListener(attributesListener); 686 super.emptyTable(); 687 } 688 689 private boolean isViewable(Attribute attribute) { 690 Object data = attribute.getValue(); 691 return XDataViewer.isViewableValue(data); 692 693 } 694 695 synchronized void removeAttributes() { 696 if (attributes != null) { 697 attributes.clear(); 698 } 699 if (unavailableAttributes != null) { 700 unavailableAttributes.clear(); 701 } 702 if (viewableAttributes != null) { 703 viewableAttributes.clear(); 704 } 705 mbean = null; 706 } 707 708 private ZoomedCell getZoomedCell(XMBean mbean, String attribute, Object value) { 709 synchronized (viewersCache) { 710 HashMap<String, ZoomedCell> viewers; 711 if (viewersCache.containsKey(mbean)) { 712 viewers = viewersCache.get(mbean); 713 } else { 714 viewers = new HashMap<String, ZoomedCell>(); 715 } 716 ZoomedCell cell; 717 if (viewers.containsKey(attribute)) { 718 cell = viewers.get(attribute); 719 cell.setValue(value); 720 if (cell.isMaximized() && cell.getType() != XDataViewer.NUMERIC) { 721 // Plotters are the only viewers with auto update capabilities. 722 // Other viewers need to be updated manually. 723 Component comp = 724 mbeansTab.getDataViewer().createAttributeViewer( 725 value, mbean, attribute, XMBeanAttributes.this); 726 cell.init(cell.getMinRenderer(), comp, cell.getMinHeight()); 727 XDataViewer.registerForMouseEvent(comp, mouseListener); 728 } 729 } else { 730 cell = new ZoomedCell(value); 731 viewers.put(attribute, cell); 732 } 733 viewersCache.put(mbean, viewers); 734 return cell; 735 } 736 } 737 738 //will be called in a synchronized block 739 protected void addTableData(DefaultTableModel tableModel, 740 XMBean mbean, 741 MBeanAttributeInfo[] attributesInfo, 742 HashMap<String, Object> attributes, 743 HashMap<String, Object> unavailableAttributes, 744 HashMap<String, Object> viewableAttributes) { 745 746 Object rowData[] = new Object[2]; 747 int col1Width = 0; 748 int col2Width = 0; 749 for (int i = 0; i < attributesInfo.length; i++) { 750 rowData[0] = (attributesInfo[i].getName()); 751 if (unavailableAttributes.containsKey(rowData[0])) { 752 rowData[1] = Messages.UNAVAILABLE; 753 } else if (viewableAttributes.containsKey(rowData[0])) { 754 rowData[1] = viewableAttributes.get(rowData[0]); 755 if (!attributesInfo[i].isWritable() || 756 !Utils.isEditableType(attributesInfo[i].getType())) { 757 rowData[1] = getZoomedCell(mbean, (String) rowData[0], rowData[1]); 758 } 759 } else { 760 rowData[1] = attributes.get(rowData[0]); 761 } 762 763 tableModel.addRow(rowData); 764 765 //Update column width 766 // 767 String str = null; 768 if(rowData[0] != null) { 769 str = rowData[0].toString(); 770 if(str.length() > col1Width) 771 col1Width = str.length(); 772 } 773 if(rowData[1] != null) { 774 str = rowData[1].toString(); 775 if(str.length() > col2Width) 776 col2Width = str.length(); 777 } 778 } 779 updateColumnWidth(col1Width, col2Width); 780 } 781 782 private void updateColumnWidth(int col1Width, int col2Width) { 783 TableColumnModel colModel = getColumnModel(); 784 785 //Get the column at index pColumn, and set its preferred width. 786 col1Width = col1Width * 7; 787 col2Width = col2Width * 7; 788 if(col1Width + col2Width < 789 (int) getPreferredScrollableViewportSize().getWidth()) 790 col2Width = (int) getPreferredScrollableViewportSize().getWidth() 791 - col1Width; 792 793 colModel.getColumn(NAME_COLUMN).setPreferredWidth(50); 794 } 795 796 class AttributesMouseListener extends MouseAdapter { 797 798 @Override 799 public void mousePressed(MouseEvent e) { 800 if(e.getButton() == MouseEvent.BUTTON1) { 801 if(e.getClickCount() >= 2) { 802 803 int row = XMBeanAttributes.this.getSelectedRow(); 804 int col = XMBeanAttributes.this.getSelectedColumn(); 805 if(col != VALUE_COLUMN) return; 806 if(col == -1 || row == -1) return; 807 808 XMBeanAttributes.this.updateZoomedCell(row, col); 809 } 810 } 811 } 812 } 813 814 class ValueCellEditor extends XTextFieldEditor { 815 // implements javax.swing.table.TableCellEditor 816 @Override 817 public Component getTableCellEditorComponent(JTable table, 818 Object value, 819 boolean isSelected, 820 int row, 821 int column) { 822 Object val = value; 823 if(column == VALUE_COLUMN) { 824 Object obj = getModel().getValueAt(row, 825 column); 826 if(obj instanceof ZoomedCell) { 827 ZoomedCell cell = (ZoomedCell) obj; 828 if(cell.getRenderer() instanceof MaximizedCellRenderer) { 829 MaximizedCellRenderer zr = 830 (MaximizedCellRenderer) cell.getRenderer(); 831 return zr.getComponent(); 832 } 833 } else { 834 Component comp = super.getTableCellEditorComponent( 835 table, val, isSelected, row, column); 836 if (isCellError(row, column) || 837 !isWritable(row) || 838 !Utils.isEditableType(getClassName(row))) { 839 textField.setEditable(false); 840 } 841 return comp; 842 } 843 } 844 return super.getTableCellEditorComponent(table, 845 val, 846 isSelected, 847 row, 848 column); 849 } 850 @Override 851 public boolean stopCellEditing() { 852 int editingRow = getEditingRow(); 853 int editingColumn = getEditingColumn(); 854 if (editingColumn == VALUE_COLUMN) { 855 Object obj = getModel().getValueAt(editingRow, editingColumn); 856 if (obj instanceof ZoomedCell) { 857 ZoomedCell cell = (ZoomedCell) obj; 858 if (cell.isMaximized()) { 859 this.cancelCellEditing(); 860 return true; 861 } 862 } 863 } 864 return super.stopCellEditing(); 865 } 866 } 867 868 class MaximizedCellRenderer extends DefaultTableCellRenderer { 869 Component comp; 870 MaximizedCellRenderer(Component comp) { 871 this.comp = comp; 872 Dimension d = comp.getPreferredSize(); 873 if (d.getHeight() > 220) { 874 comp.setPreferredSize(new Dimension((int) d.getWidth(), 220)); 875 } 876 } 877 @Override 878 public Component getTableCellRendererComponent(JTable table, 879 Object value, 880 boolean isSelected, 881 boolean hasFocus, 882 int row, 883 int column) { 884 return comp; 885 } 886 public Component getComponent() { 887 return comp; 888 } 889 } 890 891 class ZoomedCell { 892 TableCellRenderer minRenderer; 893 MaximizedCellRenderer maxRenderer; 894 int minHeight; 895 boolean minimized = true; 896 boolean init = false; 897 int type; 898 Object value; 899 ZoomedCell(Object value) { 900 type = XDataViewer.getViewerType(value); 901 this.value = value; 902 } 903 904 boolean isInited() { 905 return init; 906 } 907 908 Object getValue() { 909 return value; 910 } 911 912 void setValue(Object value) { 913 this.value = value; 914 } 915 916 void init(TableCellRenderer minRenderer, 917 Component maxComponent, 918 int minHeight) { 919 this.minRenderer = minRenderer; 920 this.maxRenderer = new MaximizedCellRenderer(maxComponent); 921 922 this.minHeight = minHeight; 923 init = true; 924 } 925 926 int getType() { 927 return type; 928 } 929 930 void reset() { 931 init = false; 932 minimized = true; 933 } 934 935 void switchState() { 936 minimized = !minimized; 937 } 938 boolean isMaximized() { 939 return !minimized; 940 } 941 void minimize() { 942 minimized = true; 943 } 944 945 void maximize() { 946 minimized = false; 947 } 948 949 int getHeight() { 950 if(minimized) return minHeight; 951 else 952 return (int) maxRenderer.getComponent(). 953 getPreferredSize().getHeight() ; 954 } 955 956 int getMinHeight() { 957 return minHeight; 958 } 959 960 @Override 961 public String toString() { 962 963 if(value == null) return null; 964 965 if(value.getClass().isArray()) { 966 String name = 967 Utils.getArrayClassName(value.getClass().getName()); 968 int length = Array.getLength(value); 969 return name + "[" + length +"]"; 970 } 971 972 if(value instanceof CompositeData || 973 value instanceof TabularData) 974 return value.getClass().getName(); 975 976 return value.toString(); 977 } 978 979 TableCellRenderer getRenderer() { 980 if(minimized) return minRenderer; 981 else return maxRenderer; 982 } 983 984 TableCellRenderer getMinRenderer() { 985 return minRenderer; 986 } 987 } 988 989 class AttributesListener implements TableModelListener { 990 991 private Component component; 992 993 public AttributesListener(Component component) { 994 this.component = component; 995 } 996 997 // Call this in EDT 998 public void tableChanged(final TableModelEvent e) { 999 // only post changes to the draggable column 1000 if (isColumnEditable(e.getColumn())) { 1001 final TableModel model = (TableModel)e.getSource(); 1002 Object tableValue = model.getValueAt(e.getFirstRow(), 1003 e.getColumn()); 1004 1005 if (LOGGER.isLoggable(Level.TRACE)) { 1006 LOGGER.log(Level.TRACE, 1007 "tableChanged: firstRow="+e.getFirstRow()+ 1008 ", lastRow="+e.getLastRow()+", column="+e.getColumn()+ 1009 ", value="+tableValue); 1010 } 1011 // if it's a String, try construct new value 1012 // using the defined type. 1013 if (tableValue instanceof String) { 1014 try { 1015 tableValue = 1016 Utils.createObjectFromString(getClassName(e.getFirstRow()), // type 1017 (String)tableValue);// value 1018 } catch (Throwable ex) { 1019 popupAndLog(ex,"tableChanged", 1020 Messages.PROBLEM_SETTING_ATTRIBUTE); 1021 } 1022 } 1023 final String attributeName = getValueName(e.getFirstRow()); 1024 final Attribute attribute = 1025 new Attribute(attributeName,tableValue); 1026 setAttribute(attribute, "tableChanged"); 1027 } 1028 } 1029 1030 // Call this in EDT 1031 private void setAttribute(final Attribute attribute, final String method) { 1032 final SwingWorker<Void,Void> setAttribute = 1033 new SwingWorker<Void,Void>() { 1034 @Override 1035 protected Void doInBackground() throws Exception { 1036 try { 1037 if (JConsole.isDebug()) { 1038 System.err.println("setAttribute("+ 1039 attribute.getName()+ 1040 "="+attribute.getValue()+")"); 1041 } 1042 mbean.setAttribute(attribute); 1043 } catch (Throwable ex) { 1044 popupAndLog(ex,method,Messages.PROBLEM_SETTING_ATTRIBUTE); 1045 } 1046 return null; 1047 } 1048 @Override 1049 protected void done() { 1050 try { 1051 get(); 1052 } catch (Exception x) { 1053 if (JConsole.isDebug()) 1054 x.printStackTrace(); 1055 } 1056 refreshAttributes(false); 1057 } 1058 1059 }; 1060 mbeansTab.workerAdd(setAttribute); 1061 } 1062 1063 // Call this outside EDT 1064 private void popupAndLog(Throwable ex, String method, String title) { 1065 ex = Utils.getActualException(ex); 1066 if (JConsole.isDebug()) ex.printStackTrace(); 1067 1068 String message = (ex.getMessage() != null) ? ex.getMessage() 1069 : ex.toString(); 1070 EventQueue.invokeLater( 1071 new ThreadDialog(component, message+"\n", 1072 title, 1073 JOptionPane.ERROR_MESSAGE)); 1074 } 1075 } 1076 } 1077