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