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 
  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.util.logging.Level;
  44 import java.util.logging.Logger;
  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.Resources;
  70 import sun.tools.jconsole.MBeansTab;
  71 import sun.tools.jconsole.JConsole;
  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             Logger.getLogger(XMBeanAttributes.class.getPackage().getName());
  85 
  86     private final static String[] columnNames =
  87     {Resources.getText("Name"),
  88      Resources.getText("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 
 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
 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 
 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
 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 
 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 
 192     public void cancelCellEditing() {
 193         if (LOGGER.isLoggable(Level.FINER)) {
 194             LOGGER.finer("Cancel Editing Row: "+getEditingRow());
 195         }
 196         final TableCellEditor tableCellEditor = getCellEditor();
 197         if (tableCellEditor != null) {
 198             tableCellEditor.cancelCellEditing();
 199         }
 200     }
 201 
 202     public void stopCellEditing() {
 203         if (LOGGER.isLoggable(Level.FINER)) {
 204             LOGGER.finer("Stop Editing Row: "+getEditingRow());
 205         }
 206         final TableCellEditor tableCellEditor = getCellEditor();
 207         if (tableCellEditor != null) {
 208             tableCellEditor.stopCellEditing();
 209         }
 210     }
 211 
 212     @Override
 213     public final boolean editCellAt(final int row, final int column, EventObject e) {
 214         if (LOGGER.isLoggable(Level.FINER)) {
 215             LOGGER.finer("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
 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
 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 
 261     public boolean isTableEditable() {
 262         return true;
 263     }
 264 
 265     public void setTableValue(Object value, int row) {
 266     }
 267 
 268     public boolean isColumnEditable(int column) {
 269         if (column < getColumnCount()) {
 270             return getColumnName(column).equals(Resources.getText("Value"));
 271         }
 272         else {
 273             return false;
 274         }
 275     }
 276 
 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 
 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 
 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
 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 = Resources.getText("Double click to expand/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 
 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
 347     public synchronized int getRowCount() {
 348         return super.getRowCount();
 349     }
 350 
 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 
 361     public synchronized boolean isCellError(int row, int col) {
 362         return (isColumnEditable(col) &&
 363                 (unavailableAttributes.containsKey(getValueName(row))));
 364     }
 365 
 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
 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.
 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                     mbeansTab.getDataViewer().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                     mbeansTab.getDataViewer().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 synchronzed 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] = Resources.getText("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     @SuppressWarnings("serial")
 815     class ValueCellEditor extends XTextFieldEditor {
 816         // implements javax.swing.table.TableCellEditor
 817         @Override
 818         public Component getTableCellEditorComponent(JTable table,
 819                                                      Object value,
 820                                                      boolean isSelected,
 821                                                      int row,
 822                                                      int column) {
 823             Object val = value;
 824             if(column == VALUE_COLUMN) {
 825                 Object obj = getModel().getValueAt(row,
 826                                                    column);
 827                 if(obj instanceof ZoomedCell) {
 828                     ZoomedCell cell = (ZoomedCell) obj;
 829                     if(cell.getRenderer() instanceof MaximizedCellRenderer) {
 830                         MaximizedCellRenderer zr =
 831                             (MaximizedCellRenderer) cell.getRenderer();
 832                         return zr.getComponent();
 833                     }
 834                 } else {
 835                     Component comp = super.getTableCellEditorComponent(
 836                             table, val, isSelected, row, column);
 837                     if (isCellError(row, column) ||
 838                         !isWritable(row) ||
 839                         !Utils.isEditableType(getClassName(row))) {
 840                         textField.setEditable(false);
 841                     }
 842                     return comp;
 843                 }
 844             }
 845             return super.getTableCellEditorComponent(table,
 846                                                      val,
 847                                                      isSelected,
 848                                                      row,
 849                                                      column);
 850         }
 851         @Override
 852         public boolean stopCellEditing() {
 853             int editingRow = getEditingRow();
 854             int editingColumn = getEditingColumn();
 855             if (editingColumn == VALUE_COLUMN) {
 856                 Object obj = getModel().getValueAt(editingRow, editingColumn);
 857                 if (obj instanceof ZoomedCell) {
 858                     ZoomedCell cell = (ZoomedCell) obj;
 859                     if (cell.isMaximized()) {
 860                         this.cancelCellEditing();
 861                         return true;
 862                     }
 863                 }
 864             }
 865             return super.stopCellEditing();
 866         }
 867     }
 868 
 869     @SuppressWarnings("serial")
 870     class MaximizedCellRenderer extends  DefaultTableCellRenderer {
 871         Component comp;
 872         MaximizedCellRenderer(Component comp) {
 873             this.comp = comp;
 874             Dimension d = comp.getPreferredSize();
 875             if (d.getHeight() > 220) {
 876                 comp.setPreferredSize(new Dimension((int) d.getWidth(), 220));
 877             }
 878         }
 879         @Override
 880         public Component getTableCellRendererComponent(JTable table,
 881                                                        Object value,
 882                                                        boolean isSelected,
 883                                                        boolean hasFocus,
 884                                                        int row,
 885                                                        int column) {
 886             return comp;
 887         }
 888         public Component getComponent() {
 889             return comp;
 890         }
 891     }
 892 
 893     class ZoomedCell {
 894         TableCellRenderer minRenderer;
 895         MaximizedCellRenderer maxRenderer;
 896         int minHeight;
 897         boolean minimized = true;
 898         boolean init = false;
 899         int type;
 900         Object value;
 901         ZoomedCell(Object value) {
 902             type = XDataViewer.getViewerType(value);
 903             this.value = value;
 904         }
 905 
 906         boolean isInited() {
 907             return init;
 908         }
 909 
 910         Object getValue() {
 911             return value;
 912         }
 913 
 914         void setValue(Object value) {
 915             this.value = value;
 916         }
 917 
 918         void init(TableCellRenderer minRenderer,
 919                   Component maxComponent,
 920                   int minHeight) {
 921             this.minRenderer = minRenderer;
 922             this.maxRenderer = new MaximizedCellRenderer(maxComponent);
 923 
 924             this.minHeight = minHeight;
 925             init = true;
 926         }
 927 
 928         int getType() {
 929             return type;
 930         }
 931 
 932         void reset() {
 933             init = false;
 934             minimized = true;
 935         }
 936 
 937         void switchState() {
 938             minimized = !minimized;
 939         }
 940         boolean isMaximized() {
 941             return !minimized;
 942         }
 943         void minimize() {
 944             minimized = true;
 945         }
 946 
 947         void maximize() {
 948             minimized = false;
 949         }
 950 
 951         int getHeight() {
 952             if(minimized) return minHeight;
 953             else
 954                 return (int) maxRenderer.getComponent().
 955                     getPreferredSize().getHeight() ;
 956         }
 957 
 958         int getMinHeight() {
 959             return minHeight;
 960         }
 961 
 962         @Override
 963         public String toString() {
 964 
 965             if(value == null) return null;
 966 
 967             if(value.getClass().isArray()) {
 968                 String name =
 969                     Utils.getArrayClassName(value.getClass().getName());
 970                 int length = Array.getLength(value);
 971                 return name + "[" + length +"]";
 972             }
 973 
 974             if(value instanceof CompositeData ||
 975                value instanceof TabularData)
 976                 return value.getClass().getName();
 977 
 978             return value.toString();
 979         }
 980 
 981         TableCellRenderer getRenderer() {
 982             if(minimized) return minRenderer;
 983             else return maxRenderer;
 984         }
 985 
 986         TableCellRenderer getMinRenderer() {
 987             return minRenderer;
 988         }
 989     }
 990 
 991     class AttributesListener implements  TableModelListener {
 992 
 993         private Component component;
 994 
 995         public AttributesListener(Component component) {
 996             this.component = component;
 997         }
 998 
 999         // Call this in EDT
1000         public void tableChanged(final TableModelEvent e) {
1001             // only post changes to the draggable column
1002             if (isColumnEditable(e.getColumn())) {
1003                 final TableModel model = (TableModel)e.getSource();
1004                 Object tableValue = model.getValueAt(e.getFirstRow(),
1005                                                  e.getColumn());
1006 
1007                 if (LOGGER.isLoggable(Level.FINER)) {
1008                     LOGGER.finer("tableChanged: firstRow="+e.getFirstRow()+
1009                         ", lastRow="+e.getLastRow()+", column="+e.getColumn()+
1010                         ", value="+tableValue);
1011                 }
1012                 // if it's a String, try construct new value
1013                 // using the defined type.
1014                 if (tableValue instanceof String) {
1015                     try {
1016                         tableValue =
1017                             Utils.createObjectFromString(getClassName(e.getFirstRow()), // type
1018                             (String)tableValue);// value
1019                     } catch (Throwable ex) {
1020                         popupAndLog(ex,"tableChanged",
1021                                 "Problem setting attribute");
1022                     }
1023                 }
1024                 final String attributeName = getValueName(e.getFirstRow());
1025                 final Attribute attribute =
1026                       new Attribute(attributeName,tableValue);
1027                 setAttribute(attribute, "tableChanged");
1028             }
1029         }
1030 
1031         // Call this in EDT
1032         private void setAttribute(final Attribute attribute, final String method) {
1033             final SwingWorker<Void,Void> setAttribute =
1034                     new SwingWorker<Void,Void>() {
1035                 @Override
1036                 protected Void doInBackground() throws Exception {
1037                     try {
1038                         if (JConsole.isDebug()) {
1039                             System.err.println("setAttribute("+
1040                                     attribute.getName()+
1041                                 "="+attribute.getValue()+")");
1042                         }
1043                         mbean.setAttribute(attribute);
1044                     } catch (Throwable ex) {
1045                         popupAndLog(ex,method,"Problem setting attribute");
1046                     }
1047                     return null;
1048                 }
1049                 @Override
1050                 protected void done() {
1051                     try {
1052                         get();
1053                     } catch (Exception x) {
1054                         if (JConsole.isDebug())
1055                             x.printStackTrace();
1056                     }
1057                     refreshAttributes(false);
1058                 }
1059 
1060             };
1061             mbeansTab.workerAdd(setAttribute);
1062         }
1063 
1064         // Call this outside EDT
1065         private void popupAndLog(Throwable ex, String method, String key) {
1066             ex = Utils.getActualException(ex);
1067             if (JConsole.isDebug()) ex.printStackTrace();
1068 
1069             String message = (ex.getMessage() != null) ? ex.getMessage()
1070                     : ex.toString();
1071             EventQueue.invokeLater(
1072                     new ThreadDialog(component, message+"\n",
1073                                      Resources.getText(key),
1074                                      JOptionPane.ERROR_MESSAGE));
1075         }
1076     }
1077 }