jdk/src/share/classes/sun/tools/jconsole/inspector/XMBeanAttributes.java
author lmalvent
Tue, 10 Jun 2008 13:50:06 +0200
changeset 697 fb86c99b92a2
parent 2 90ce3da70b43
child 715 f16baef3a20e
permissions -rw-r--r--
6711106: REGRESSION: Bad usage of SnapshotMBeanServerConnection in MBeans tab and JConsole plugins. Reviewed-by: jfdenise

/*
 * Copyright 2004-2007 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package sun.tools.jconsole.inspector;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import javax.swing.tree.*;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.FlowLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.*;
import java.awt.Insets;
import java.awt.Dimension;
import java.util.*;
import java.io.*;

import java.lang.reflect.Array;

import javax.management.*;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.TabularData;

import sun.tools.jconsole.Resources;
import sun.tools.jconsole.MBeansTab;
import sun.tools.jconsole.Plotter;
import sun.tools.jconsole.JConsole;
import sun.tools.jconsole.ProxyClient.SnapshotMBeanServerConnection;

/*IMPORTANT :
  There is a deadlock issue there if we don't synchronize well loadAttributes,
  refresh attributes and empty table methods since a UI thread can call
  loadAttributes and at the same time a JMX notification can raise an
  emptyTable. Since there are synchronization in the JMX world it's
  COMPULSORY to not call the JMX world in synchronized blocks */
@SuppressWarnings("serial")
public class XMBeanAttributes extends XTable {
    private final static String[] columnNames =
    {Resources.getText("Name"),
     Resources.getText("Value")};

    private boolean editable = true;

    private XMBean mbean;
    private MBeanInfo mbeanInfo;
    private MBeanAttributeInfo[] attributesInfo;
    private HashMap<String, Object> attributes;
    private HashMap<String, Object> unavailableAttributes;
    private HashMap<String, Object> viewableAttributes;
    private WeakHashMap<XMBean, HashMap<String, ZoomedCell>> viewersCache =
            new WeakHashMap<XMBean, HashMap<String, ZoomedCell>>();
    private TableModelListener attributesListener;
    private MBeansTab mbeansTab;
    private XTable table;
    private TableCellEditor valueCellEditor = new ValueCellEditor();
    private int rowMinHeight = -1;
    private AttributesMouseListener mouseListener = new AttributesMouseListener();

    private static TableCellEditor editor =
            new Utils.ReadOnlyTableCellEditor(new JTextField());

    public XMBeanAttributes(MBeansTab mbeansTab) {
        super();
        this.mbeansTab = mbeansTab;
        ((DefaultTableModel)getModel()).setColumnIdentifiers(columnNames);
        getModel().addTableModelListener(attributesListener =
                                         new AttributesListener(this));
        getColumnModel().getColumn(NAME_COLUMN).setPreferredWidth(40);

        addMouseListener(mouseListener);
        getTableHeader().setReorderingAllowed(false);
        setColumnEditors();
        addKeyListener(new Utils.CopyKeyAdapter());
    }

    public synchronized Component prepareRenderer(TableCellRenderer renderer,
                                                  int row, int column) {
        //In case we have a repaint thread that is in the process of
        //repainting an obsolete table, just ignore the call.
        //It can happen when MBean selection is switched at a very quick rate
        if(row >= getRowCount())
            return null;
        else
            return super.prepareRenderer(renderer, row, column);
    }

    void updateRowHeight(Object obj, int row) {
        ZoomedCell cell = null;
        if(obj instanceof ZoomedCell) {
            cell = (ZoomedCell) obj;
            if(cell.isInited())
                setRowHeight(row, cell.getHeight());
            else
                if(rowMinHeight != - 1)
                    setRowHeight(row, rowMinHeight);
        } else
            if(rowMinHeight != - 1)
                setRowHeight(row, rowMinHeight);
    }

    public synchronized TableCellRenderer getCellRenderer(int row,
            int column) {
        //In case we have a repaint thread that is in the process of
        //repainting an obsolete table, just ignore the call.
        //It can happen when MBean selection is switched at a very quick rate
        if (row >= getRowCount()) {
            return null;
        } else {
            if (column == VALUE_COLUMN) {
                Object obj = getModel().getValueAt(row, column);
                if (obj instanceof ZoomedCell) {
                    ZoomedCell cell = (ZoomedCell) obj;
                    if (cell.isInited()) {
                        DefaultTableCellRenderer renderer =
                                (DefaultTableCellRenderer) cell.getRenderer();
                        renderer.setToolTipText(getToolTip(row,column));
                        return renderer;
                    }
                }
            }
            DefaultTableCellRenderer renderer = (DefaultTableCellRenderer)
                super.getCellRenderer(row, column);
            if (!isCellError(row, column)) {
                if (!(isColumnEditable(column) && isWritable(row) &&
                      Utils.isEditableType(getClassName(row)))) {
                    renderer.setForeground(getDefaultColor());
                }
            }
            return renderer;
        }
    }

    private void setColumnEditors() {
        TableColumnModel tcm = getColumnModel();
        for (int i = 0; i < columnNames.length; i++) {
            TableColumn tc = tcm.getColumn(i);
            if (isColumnEditable(i)) {
                tc.setCellEditor(valueCellEditor);
            } else {
                tc.setCellEditor(editor);
            }
        }
    }

    public void cancelCellEditing() {
        TableCellEditor editor = getCellEditor();
        if (editor != null) {
            editor.cancelCellEditing();
        }
    }

    public void stopCellEditing() {
        TableCellEditor editor = getCellEditor();
        if (editor != null) {
            editor.stopCellEditing();
        }
    }

    public final boolean editCellAt(int row, int column, EventObject e) {
        boolean retVal = super.editCellAt(row, column, e);
        if (retVal) {
            TableCellEditor editor =
                    getColumnModel().getColumn(column).getCellEditor();
            if (editor == valueCellEditor) {
                ((JComponent) editor).requestFocus();
            }
        }
        return retVal;
    }

    @Override
    public boolean isCellEditable(int row, int col) {
        // All the cells in non-editable columns are editable
        if (!isColumnEditable(col)) {
            return true;
        }
        // Maximized zoomed cells are editable
        Object obj = getModel().getValueAt(row, col);
        if (obj instanceof ZoomedCell) {
            ZoomedCell cell = (ZoomedCell) obj;
            return cell.isMaximized();
        }
        return true;
    }

    @Override
    public void setValueAt(Object value, int row, int column) {
        if (!isCellError(row, column) && isColumnEditable(column) &&
            isWritable(row) && Utils.isEditableType(getClassName(row))) {
            super.setValueAt(value, row, column);
        }
    }

    //Table methods

    public boolean isTableEditable() {
        return true;
    }

    public void setTableValue(Object value, int row) {
    }

    public boolean isColumnEditable(int column) {
        if (column < getColumnCount()) {
            return getColumnName(column).equals(Resources.getText("Value"));
        }
        else {
            return false;
        }
    }

    public String getClassName(int row) {
        int index = convertRowToIndex(row);
        if (index != -1) {
            return attributesInfo[index].getType();
        }
        else {
            return null;
        }
    }


    public String getValueName(int row) {
        int index = convertRowToIndex(row);
        if (index != -1) {
            return attributesInfo[index].getName();
        }
        else {
            return null;
        }
    }


    public Object getValue(int row) {
        return ((DefaultTableModel) getModel()).getValueAt(row, VALUE_COLUMN);
    }

    //tool tip only for editable column
    public String getToolTip(int row, int column) {
        if (isCellError(row, column)) {
            return (String) unavailableAttributes.get(getValueName(row));
        }
        if (isColumnEditable(column)) {
            Object value = getValue(row);
            String tip = null;
            if (value != null) {
                tip = value.toString();
                if(isAttributeViewable(row, VALUE_COLUMN))
                    tip = Resources.getText("Double click to expand/collapse")+
                        ". " + tip;
            }

            return tip;
        }

        if(column == NAME_COLUMN) {
            int index = convertRowToIndex(row);
            if (index != -1) {
                return attributesInfo[index].getDescription();
            }
        }
        return null;
    }

    public synchronized boolean isWritable(int row) {
        int index = convertRowToIndex(row);
        if (index != -1) {
            return (attributesInfo[index].isWritable());
        }
        else {
            return false;
        }
    }

    /**
     * Override JTable method in order to make any call to this method
     * atomic with TableModel elements.
     */
    public synchronized int getRowCount() {
        return super.getRowCount();
    }

    public synchronized boolean isReadable(int row) {
        int index = convertRowToIndex(row);
        if (index != -1) {
            return (attributesInfo[index].isReadable());
        }
        else {
            return false;
        }
    }

    public synchronized boolean isCellError(int row, int col) {
        return (isColumnEditable(col) &&
                (unavailableAttributes.containsKey(getValueName(row))));
    }

    public synchronized boolean isAttributeViewable(int row, int col) {
        boolean isViewable = false;
        if(col == VALUE_COLUMN) {
            Object obj = getModel().getValueAt(row, col);
            if(obj instanceof ZoomedCell)
                isViewable = true;
        }

        return isViewable;
    }

    public void loadAttributes(final XMBean mbean, MBeanInfo mbeanInfo) {
        // To avoid deadlock with events coming from the JMX side,
        // we retrieve all JMX stuff in a non synchronized block.

        if(mbean == null) return;

        final MBeanAttributeInfo[] attributesInfo = mbeanInfo.getAttributes();
        final HashMap<String, Object> attributes =
            new HashMap<String, Object>(attributesInfo.length);
        final HashMap<String, Object> unavailableAttributes =
            new HashMap<String, Object>(attributesInfo.length);
        final HashMap<String, Object> viewableAttributes =
            new HashMap<String, Object>(attributesInfo.length);
        AttributeList list = null;

        try {
            list = mbean.getAttributes(attributesInfo);
        } catch (Exception e) {
            if (JConsole.isDebug()) {
                System.err.println("Error calling getAttributes() on MBean \"" +
                                   mbean.getObjectName() + "\". JConsole will " +
                                   "try to get them individually calling " +
                                   "getAttribute() instead. Exception:");
                e.printStackTrace(System.err);
            }
            list = new AttributeList();
            //Can't load all attributes, do it one after each other.
            for(int i = 0; i < attributesInfo.length; i++) {
                String name = null;
                try {
                    name = attributesInfo[i].getName();
                    Object value =
                        mbean.getMBeanServerConnection().getAttribute(mbean.getObjectName(), name);
                    list.add(new Attribute(name, value));
                }catch(Exception ex) {
                    if(attributesInfo[i].isReadable()) {
                        unavailableAttributes.put(name,
                                                  Utils.getActualException(ex).
                                                  toString());
                    }
                }
            }
        }
        try {
            int att_length = list.size();
            for (int i=0;i<att_length;i++) {
                Attribute attribute = (Attribute) list.get(i);
                if(isViewable(attribute)) {
                    viewableAttributes.put(attribute.getName(),
                                           attribute.getValue());
                }
                else
                    attributes.put(attribute.getName(),attribute.getValue());

            }
            // if not all attributes are accessible,
            // check them one after the other.
            if (att_length < attributesInfo.length) {
                for (int i=0;i<attributesInfo.length;i++) {
                    MBeanAttributeInfo attributeInfo = attributesInfo[i];
                    if (!attributes.containsKey(attributeInfo.getName()) &&
                        !viewableAttributes.containsKey(attributeInfo.
                                                        getName()) &&
                        !unavailableAttributes.containsKey(attributeInfo.
                                                           getName())) {
                        if (attributeInfo.isReadable()) {
                            // getAttributes didn't help resolving the
                            // exception.
                            // We must call it again to understand what
                            // went wrong.
                            try {
                                Object v =
                                    mbean.getMBeanServerConnection().getAttribute(
                                    mbean.getObjectName(), attributeInfo.getName());
                                //What happens if now it is ok?
                                // Be pragmatic, add it to readable...
                                attributes.put(attributeInfo.getName(),
                                               v);
                            }catch(Exception e) {
                                //Put the exception that will be displayed
                                // in tooltip
                                unavailableAttributes.put(attributeInfo.
                                                          getName(),
                                                          Utils.
                                                          getActualException(e)
                                                          .toString());
                            }
                        }
                    }
                }
            }
        }
        catch(Exception e) {
            //sets all attributes unavailable except the writable ones
            for (int i=0;i<attributesInfo.length;i++) {
                MBeanAttributeInfo attributeInfo = attributesInfo[i];
                if (attributeInfo.isReadable()) {
                    unavailableAttributes.put(attributeInfo.getName(),
                                              Utils.getActualException(e).
                                              toString());
                }
            }
        }
        //end of retrieval

        //one update at a time
        synchronized(this) {

            this.mbean = mbean;
            this.mbeanInfo = mbeanInfo;
            this.attributesInfo = attributesInfo;
            this.attributes = attributes;
            this.unavailableAttributes = unavailableAttributes;
            this.viewableAttributes = viewableAttributes;

            EventQueue.invokeLater(new Runnable() {
                public void run() {
                    DefaultTableModel tableModel =
                            (DefaultTableModel) getModel();

                    // don't listen to these events
                    tableModel.removeTableModelListener(attributesListener);

                    // add attribute information
                    emptyTable();

                    addTableData(tableModel,
                                 mbean,
                                 attributesInfo,
                                 attributes,
                                 unavailableAttributes,
                                 viewableAttributes);

                    // update the model with the new data
                    tableModel.newDataAvailable(new TableModelEvent(tableModel));
                    // re-register for change events
                    tableModel.addTableModelListener(attributesListener);
                }
            });
        }
    }

    void collapse(String attributeName, final Component c) {
        final int row = getSelectedRow();
        Object obj = getModel().getValueAt(row, VALUE_COLUMN);
        if(obj instanceof ZoomedCell) {
            cancelCellEditing();
            ZoomedCell cell = (ZoomedCell) obj;
            cell.reset();
            setRowHeight(row,
                         cell.getHeight());
            editCellAt(row,
                       VALUE_COLUMN);
            invalidate();
            repaint();
        }
    }

    ZoomedCell updateZoomedCell(int row,
                                int col) {
        Object obj = getModel().getValueAt(row, VALUE_COLUMN);
        ZoomedCell cell = null;
        if(obj instanceof ZoomedCell) {
            cell = (ZoomedCell) obj;
            if(!cell.isInited()) {
                Object elem = cell.getValue();
                String attributeName =
                    (String) getModel().getValueAt(row,
                                                   NAME_COLUMN);
                Component comp = mbeansTab.getDataViewer().
                        createAttributeViewer(elem, mbean, attributeName, this);
                if(comp != null){
                    if(rowMinHeight == -1)
                        rowMinHeight = getRowHeight(row);

                    cell.init(super.getCellRenderer(row, col),
                              comp,
                              rowMinHeight);

                    mbeansTab.getDataViewer().registerForMouseEvent(
                            comp, mouseListener);
                } else
                    return cell;
            }

            cell.switchState();
            setRowHeight(row,
                         cell.getHeight());

            if(!cell.isMaximized()) {
                cancelCellEditing();
                //Back to simple editor.
                editCellAt(row,
                           VALUE_COLUMN);
            }

            invalidate();
            repaint();
        }
        return cell;
    }

     public void refreshAttributes() {
         SnapshotMBeanServerConnection mbsc = mbeansTab.getSnapshotMBeanServerConnection();
         mbsc.flush();
         stopCellEditing();
         loadAttributes(mbean, mbeanInfo);
     }


     public void emptyTable() {
         synchronized(this) {
             ((DefaultTableModel) getModel()).
                 removeTableModelListener(attributesListener);
             super.emptyTable();
         }
     }

    private boolean isViewable(Attribute attribute) {
        Object data = attribute.getValue();
        return XDataViewer.isViewableValue(data);

    }

    synchronized void removeAttributes() {
        if (attributes != null) {
            attributes.clear();
        }
        if (unavailableAttributes != null) {
            unavailableAttributes.clear();
        }
        if (viewableAttributes != null) {
            viewableAttributes.clear();
        }
        mbean = null;
    }

    private ZoomedCell getZoomedCell(XMBean mbean, String attribute, Object value) {
        synchronized (viewersCache) {
            HashMap<String, ZoomedCell> viewers;
            if (viewersCache.containsKey(mbean)) {
                viewers = viewersCache.get(mbean);
            } else {
                viewers = new HashMap<String, ZoomedCell>();
            }
            ZoomedCell cell;
            if (viewers.containsKey(attribute)) {
                cell = viewers.get(attribute);
                cell.setValue(value);
                if (cell.isMaximized() && cell.getType() != XDataViewer.NUMERIC) {
                    // Plotters are the only viewers with auto update capabilities.
                    // Other viewers need to be updated manually.
                    Component comp =
                        mbeansTab.getDataViewer().createAttributeViewer(
                            value, mbean, attribute, XMBeanAttributes.this);
                    cell.init(cell.getMinRenderer(), comp, cell.getMinHeight());
                    mbeansTab.getDataViewer().registerForMouseEvent(comp, mouseListener);
                }
            } else {
                cell = new ZoomedCell(value);
                viewers.put(attribute, cell);
            }
            viewersCache.put(mbean, viewers);
            return cell;
        }
    }

    //will be called in a synchronzed block
    protected void addTableData(DefaultTableModel tableModel,
                                XMBean mbean,
                                MBeanAttributeInfo[] attributesInfo,
                                HashMap<String, Object> attributes,
                                HashMap<String, Object> unavailableAttributes,
                                HashMap<String, Object> viewableAttributes) {

        Object rowData[] = new Object[2];
        int col1Width = 0;
        int col2Width = 0;
        for (int i = 0; i < attributesInfo.length; i++) {
            rowData[0] = (attributesInfo[i].getName());
            if (unavailableAttributes.containsKey(rowData[0])) {
                rowData[1] = Resources.getText("Unavailable");
            } else if (viewableAttributes.containsKey(rowData[0])) {
                rowData[1] = viewableAttributes.get(rowData[0]);
                if (!attributesInfo[i].isWritable() ||
                    !Utils.isEditableType(attributesInfo[i].getType())) {
                    rowData[1] = getZoomedCell(mbean, (String) rowData[0], rowData[1]);
                }
            } else {
                rowData[1] = attributes.get(rowData[0]);
            }

            tableModel.addRow(rowData);

            //Update column width
            //
            String str = null;
            if(rowData[0] != null) {
                str = rowData[0].toString();
                if(str.length() > col1Width)
                    col1Width = str.length();
            }
            if(rowData[1] != null) {
                str = rowData[1].toString();
                if(str.length() > col2Width)
                    col2Width = str.length();
            }
        }
        updateColumnWidth(col1Width, col2Width);
    }

    private void updateColumnWidth(int col1Width, int col2Width) {
        TableColumnModel colModel = getColumnModel();

        //Get the column at index pColumn, and set its preferred width.
        col1Width = col1Width * 7;
        col2Width = col2Width * 7;
        if(col1Width + col2Width <
           (int) getPreferredScrollableViewportSize().getWidth())
            col2Width = (int) getPreferredScrollableViewportSize().getWidth()
                - col1Width;

        colModel.getColumn(NAME_COLUMN).setPreferredWidth(50);
    }

    class AttributesMouseListener extends MouseAdapter {

        public void mousePressed(MouseEvent e) {
            if(e.getButton() == MouseEvent.BUTTON1) {
                if(e.getClickCount() >= 2) {

                    int row = XMBeanAttributes.this.getSelectedRow();
                    int col = XMBeanAttributes.this.getSelectedColumn();
                    if(col != VALUE_COLUMN) return;
                    if(col == -1 || row == -1) return;

                    XMBeanAttributes.this.updateZoomedCell(row, col);
                }
            }
        }
    }

    class ValueCellEditor extends XTextFieldEditor {
        // implements javax.swing.table.TableCellEditor
        public Component getTableCellEditorComponent(JTable table,
                                                     Object value,
                                                     boolean isSelected,
                                                     int row,
                                                     int column) {
            Object val = value;
            if(column == VALUE_COLUMN) {
                Object obj = getModel().getValueAt(row,
                                                   column);
                if(obj instanceof ZoomedCell) {
                    ZoomedCell cell = (ZoomedCell) obj;
                    if(cell.getRenderer() instanceof MaximizedCellRenderer) {
                        MaximizedCellRenderer zr =
                            (MaximizedCellRenderer) cell.getRenderer();
                        return zr.getComponent();
                    }
                } else {
                    Component comp = super.getTableCellEditorComponent(
                            table, val, isSelected, row, column);
                    if (isCellError(row, column) ||
                        !isWritable(row) ||
                        !Utils.isEditableType(getClassName(row))) {
                        textField.setEditable(false);
                    }
                    return comp;
                }
            }
            return super.getTableCellEditorComponent(table,
                                                     val,
                                                     isSelected,
                                                     row,
                                                     column);
        }
        @Override
        public boolean stopCellEditing() {
            int editingRow = getEditingRow();
            int editingColumn = getEditingColumn();
            if (editingColumn == VALUE_COLUMN) {
                Object obj = getModel().getValueAt(editingRow, editingColumn);
                if (obj instanceof ZoomedCell) {
                    ZoomedCell cell = (ZoomedCell) obj;
                    if (cell.isMaximized()) {
                        this.cancelCellEditing();
                        return true;
                    }
                }
            }
            return super.stopCellEditing();
        }
    }

    class MaximizedCellRenderer extends  DefaultTableCellRenderer {
        Component comp;
        MaximizedCellRenderer(Component comp) {
            this.comp = comp;
            Dimension d = comp.getPreferredSize();
            if (d.getHeight() > 200) {
                comp.setPreferredSize(new Dimension((int) d.getWidth(), 200));
            }
        }
        public Component getTableCellRendererComponent(JTable table,
                                                       Object value,
                                                       boolean isSelected,
                                                       boolean hasFocus,
                                                       int row,
                                                       int column) {
            return comp;
        }
        public Component getComponent() {
            return comp;
        }
    }

    class ZoomedCell {
        TableCellRenderer minRenderer;
        MaximizedCellRenderer maxRenderer;
        int minHeight;
        boolean minimized = true;
        boolean init = false;
        int type;
        Object value;
        ZoomedCell(Object value) {
            type = XDataViewer.getViewerType(value);
            this.value = value;
        }

        boolean isInited() {
            return init;
        }

        Object getValue() {
            return value;
        }

        void setValue(Object value) {
            this.value = value;
        }

        void init(TableCellRenderer minRenderer,
                  Component maxComponent,
                  int minHeight) {
            this.minRenderer = minRenderer;
            this.maxRenderer = new MaximizedCellRenderer(maxComponent);

            this.minHeight = minHeight;
            init = true;
        }

        int getType() {
            return type;
        }

        void reset() {
            init = false;
            minimized = true;
        }

        void switchState() {
            minimized = !minimized;
        }
        boolean isMaximized() {
            return !minimized;
        }
        void minimize() {
            minimized = true;
        }

        void maximize() {
            minimized = false;
        }

        int getHeight() {
            if(minimized) return minHeight;
            else
                return (int) maxRenderer.getComponent().
                    getPreferredSize().getHeight() ;
        }

        int getMinHeight() {
            return minHeight;
        }

        public String toString() {

            if(value == null) return null;

            if(value.getClass().isArray()) {
                String name =
                    Utils.getArrayClassName(value.getClass().getName());
                int length = Array.getLength(value);
                return name + "[" + length +"]";
            }

            if(value instanceof CompositeData ||
               value instanceof TabularData)
                return value.getClass().getName();

            return value.toString();
        }

        TableCellRenderer getRenderer() {
            if(minimized) return minRenderer;
            else return maxRenderer;
        }

        TableCellRenderer getMinRenderer() {
            return minRenderer;
        }
    }

    class AttributesListener implements  TableModelListener {

        private Component component;

        public AttributesListener(Component component) {
            this.component = component;
        }

        public void tableChanged(final TableModelEvent e) {
            final TableModel model = (TableModel)e.getSource();
            // only post changes to the draggable column
            if (isColumnEditable(e.getColumn())) {
                mbeansTab.workerAdd(new Runnable() {
                        public void run() {
                            try {
                                Object tableValue =
                                    model.getValueAt(e.getFirstRow(),
                                                     e.getColumn());
                                // if it's a String, try construct new value
                                // using the defined type.
                                if (tableValue instanceof String) {
                                    tableValue =
                                        Utils.createObjectFromString(getClassName(e.getFirstRow()), // type
                                                                     (String)tableValue);// value
                                }
                                String attributeName =
                                    getValueName(e.getFirstRow());
                                Attribute attribute =
                                    new Attribute(attributeName,tableValue);
                                mbean.setAttribute(attribute);
                            }
                            catch (Throwable ex) {
                                if (JConsole.isDebug()) {
                                    ex.printStackTrace();
                                }
                                ex = Utils.getActualException(ex);

                                String message = (ex.getMessage() != null) ? ex.getMessage() : ex.toString();
                                EventQueue.invokeLater(new ThreadDialog(component,
                                                                        message+"\n",
                                                                        Resources.getText("Problem setting attribute"),
                                                                        JOptionPane.ERROR_MESSAGE));
                            }
                            refreshAttributes();
                        }
                    });
            }
        }
    }
}