/* * Copyright (c) 2007, 2011, Oracle and/or its affiliates. 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.tools.visualvm.modules.mbeans; import javax.swing.*; import javax.swing.event.*; import javax.swing.table.*; import java.awt.BorderLayout; import java.awt.FlowLayout; import java.awt.Component; import java.awt.Color; import java.awt.Font; import java.awt.event.*; import java.awt.Dimension; import java.util.*; import java.lang.reflect.Array; import java.util.logging.Level; import java.util.logging.Logger; import javax.management.openmbean.*; class XOpenTypeViewer extends JPanel implements ActionListener { private final static Logger LOGGER = Logger.getLogger(XOpenTypeViewer.class.getName()); JButton prev, incr, decr, tabularPrev, tabularNext; JLabel compositeLabel, tabularLabel; JScrollPane container; XOpenTypeData current; XOpenTypeDataListener listener = new XOpenTypeDataListener(); private static final String compositeNavigationSingle = Resources.getText("LBL_MBeansTab.compositeNavigationSingle"); // NOI18N private static final String tabularNavigationSingle = Resources.getText("LBL_MBeansTab.tabularNavigationSingle"); // NOI18N private static TableCellEditor editor = new Utils.ReadOnlyTableCellEditor(new JTextField()); class XOpenTypeDataListener extends MouseAdapter { XOpenTypeDataListener() { } @Override public void mousePressed(MouseEvent e) { if(e.getButton() == MouseEvent.BUTTON1) { if(e.getClickCount() >= 2) { XOpenTypeData elem = getSelectedViewedOpenType(); if(elem != null) { try { elem.viewed(XOpenTypeViewer.this); }catch(Exception ex) { //Nothing to change, the element //can't be displayed } } } } } private XOpenTypeData getSelectedViewedOpenType() { int row = XOpenTypeViewer.this.current.getSelectedRow(); int col = XOpenTypeViewer.this.current.getSelectedColumn(); Object elem = XOpenTypeViewer.this.current.getModel().getValueAt(row, col); if(elem instanceof XOpenTypeData) return (XOpenTypeData) elem; else return null; } } static interface Navigatable { public void incrElement(); public void decrElement(); public boolean canDecrement(); public boolean canIncrement(); public int getElementCount(); public int getSelectedElementIndex(); } static interface XViewedTabularData extends Navigatable { } static interface XViewedArrayData extends Navigatable { } static abstract class XOpenTypeData extends JTable { XOpenTypeData parent; private Color defaultColor; protected int col1Width = -1; protected int col2Width = -1; private boolean init; private Font normalFont, boldFont; protected XOpenTypeData(XOpenTypeData parent) { this.parent = parent; } public XOpenTypeData getViewedParent() { return parent; } public String getToolTip(int row, int col) { if(col == XTable.VALUE_COLUMN) { Object value = getModel().getValueAt(row, col); if (value != null) { if(isClickableElement(value)) return Resources.getText("LBL_DoubleClickToVisualize") // NOI18N + ". " + value.toString(); // NOI18N else return value.toString(); } } return null; } @Override public TableCellRenderer getCellRenderer(int row, int column) { DefaultTableCellRenderer tcr = (DefaultTableCellRenderer)super.getCellRenderer(row,column); tcr.setToolTipText(getToolTip(row,column)); return tcr; } public void renderKey(String key, Component comp) { comp.setFont(normalFont); } @Override public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { Component comp = super.prepareRenderer(renderer, row, column); if (normalFont == null) { normalFont = comp.getFont(); boldFont = normalFont.deriveFont(Font.BOLD); } Object o = ((DefaultTableModel) getModel()).getValueAt(row, column); if (column == 0) { String key = o.toString(); renderKey(key, comp); } else { if (isClickableElement(o)) { comp.setFont(boldFont); } else { comp.setFont(normalFont); } } return comp; } protected boolean isClickableElement(Object obj) { if (obj instanceof XOpenTypeData) { if (obj instanceof Navigatable) { return (((Navigatable) obj).getElementCount() != 0); } else { return (obj instanceof XCompositeData); } } return false; } protected void updateColumnWidth() { if (!init) { TableColumnModel colModel = getColumnModel(); if (col2Width == -1) { col1Width = col1Width * 7; if (col1Width < getPreferredScrollableViewportSize().getWidth()) { col1Width = (int) getPreferredScrollableViewportSize().getWidth(); } colModel.getColumn(0).setPreferredWidth(col1Width); init = true; return; } col1Width = (col1Width * 7) + 7; col1Width = Math.max(col1Width, 70); col2Width = (col2Width * 7) + 7; if (col1Width + col2Width < getPreferredScrollableViewportSize().getWidth()) { col2Width = (int) getPreferredScrollableViewportSize().getWidth() - col1Width; } colModel.getColumn(0).setPreferredWidth(col1Width); colModel.getColumn(1).setPreferredWidth(col2Width); init = true; } } public abstract void viewed(XOpenTypeViewer viewer) throws Exception; protected void initTable(String[] columnNames) { setRowSelectionAllowed(false); setColumnSelectionAllowed(false); getTableHeader().setReorderingAllowed(false); ((DefaultTableModel) getModel()).setColumnIdentifiers(columnNames); for (Enumeration<TableColumn> e = getColumnModel().getColumns(); e.hasMoreElements();) { TableColumn tc = e.nextElement(); tc.setCellEditor(editor); } addKeyListener(new Utils.CopyKeyAdapter()); setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS); setPreferredScrollableViewportSize(new Dimension(350, 200)); } protected void emptyTable() { invalidate(); while (getModel().getRowCount()>0) ((DefaultTableModel) getModel()).removeRow(0); validate(); } @Override public void setValueAt(Object value, int row, int col) { } } static class TabularDataComparator implements Comparator<CompositeData> { private final List<String> indexNames; public TabularDataComparator(TabularType type) { indexNames = type.getIndexNames(); } @SuppressWarnings("unchecked") public int compare(CompositeData o1, CompositeData o2) { for (String key : indexNames) { Object c1 = o1.get(key); Object c2 = o2.get(key); if (c1 instanceof Comparable && c2 instanceof Comparable) { int result = ((Comparable) c1).compareTo(c2); if (result != 0) return result; } } return 0; } } static class XTabularData extends XCompositeData implements XViewedTabularData { final TabularData tabular; final TabularType type; int currentIndex = 0; final Object[] elements; final int size; private Font normalFont, italicFont; @SuppressWarnings("unchecked") public XTabularData(XOpenTypeData parent, TabularData tabular) { super(parent, accessFirstElement(tabular)); this.tabular = tabular; type = tabular.getTabularType(); size = tabular.values().size(); if (size > 0) { // Order tabular data elements using index names List<CompositeData> data = new ArrayList<CompositeData>( (Collection<CompositeData>) tabular.values()); Collections.sort(data, new TabularDataComparator(type)); elements = data.toArray(); loadCompositeData((CompositeData) elements[0]); } else { elements = new Object[0]; } } private static CompositeData accessFirstElement(TabularData tabular) { if(tabular.values().size() == 0) return null; return (CompositeData) tabular.values().toArray()[0]; } @Override public void renderKey(String key, Component comp) { if (normalFont == null) { normalFont = comp.getFont(); italicFont = normalFont.deriveFont(Font.ITALIC); } for(Object k : type.getIndexNames()) { if(key.equals(k)) comp.setFont(italicFont); } } public int getElementCount() { return size; } public int getSelectedElementIndex() { return currentIndex; } public void incrElement() { currentIndex++; loadCompositeData((CompositeData)elements[currentIndex]); } public void decrElement() { currentIndex--; loadCompositeData((CompositeData)elements[currentIndex]); } public boolean canDecrement() { if(currentIndex == 0) return false; else return true; } public boolean canIncrement(){ if(size == 0 || currentIndex == size -1) return false; else return true; } @Override public String toString() { return type == null ? "" : type.getDescription(); // NOI18N } } static class XCompositeData extends XOpenTypeData { protected final String[] columnNames = { Resources.getText("LBL_Name"), Resources.getText("LBL_Value") // NOI18N }; CompositeData composite; public XCompositeData() { super(null); initTable(columnNames); } //In sync with array, no init table. public XCompositeData(XOpenTypeData parent) { super(parent); } public XCompositeData(XOpenTypeData parent, CompositeData composite) { super(parent); initTable(columnNames); if(composite != null) { this.composite = composite; loadCompositeData(composite); } } public void viewed(XOpenTypeViewer viewer) throws Exception { viewer.setOpenType(this); updateColumnWidth(); } @Override public String toString() { return composite == null ? "" : // NOI18N composite.getCompositeType().getTypeName(); } protected Object formatKey(String key) { return key; } public String getToolTip(int row, int col) { if (col == XTable.NAME_COLUMN && composite != null) { String val = getModel().getValueAt(row,col).toString(); return composite.getCompositeType().getDescription(val); } return super.getToolTip(row, col); } private void load(CompositeData data) { CompositeType type = data.getCompositeType(); Set keys = type.keySet(); Iterator it = keys.iterator(); Object[] rowData = new Object[2]; while (it.hasNext()) { String key = (String) it.next(); Object val = data.get(key); rowData[0] = formatKey(key); if (val == null) { rowData[1] = ""; // NOI18N } else { OpenType openType = type.getType(key); if (openType instanceof CompositeType) { rowData[1] = new XCompositeData(this, (CompositeData) val); } else if (openType instanceof ArrayType) { rowData[1] = new XArrayData(this, (ArrayType) openType, val); } else if (openType instanceof SimpleType) { rowData[1] = val; } else if (openType instanceof TabularType) { rowData[1] = new XTabularData(this, (TabularData) val); } } // 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(); } } ((DefaultTableModel) getModel()).addRow(rowData); } } protected void loadCompositeData(CompositeData data) { composite = data; emptyTable(); load(data); DefaultTableModel tableModel = (DefaultTableModel) getModel(); tableModel.newDataAvailable(new TableModelEvent(tableModel)); } } static class XArrayData extends XCompositeData implements XViewedArrayData { private int dimension; private int size; private OpenType elemType; private Object val; private boolean isCompositeType; private boolean isTabularType; private int currentIndex; private CompositeData[] elements; private final String[] arrayColumns = {Resources.getText("LBL_Value")}; // NOI18N private Font normalFont, boldFont; XArrayData(XOpenTypeData parent, ArrayType type, Object val) { this(parent, type.getDimension(), type.getElementOpenType(), val); } XArrayData(XOpenTypeData parent, int dimension, OpenType elemType, Object val) { super(parent); this.dimension = dimension; this.elemType = elemType; this.val = val; String[] columns = null; if (dimension > 1) return; isCompositeType = (elemType instanceof CompositeType); isTabularType = (elemType instanceof TabularType); columns = isCompositeType ? columnNames : arrayColumns; initTable(columns); loadArray(); } @Override public void viewed(XOpenTypeViewer viewer) throws Exception { if (size == 0) throw new Exception(Resources.getText("LBL_EmptyArray")); // NOI18N if (dimension > 1) throw new Exception(Resources.getText("LBL_DimensionIsNotSupported") // NOI18N + ": " + dimension); // NOI18N super.viewed(viewer); } public int getElementCount() { return size; } public int getSelectedElementIndex() { return currentIndex; } @Override public void renderKey(String key, Component comp) { if (normalFont == null) { normalFont = comp.getFont(); boldFont = normalFont.deriveFont(Font.BOLD); } if (isTabularType) { comp.setFont(boldFont); } } public void incrElement() { currentIndex++; loadCompositeData(elements[currentIndex]); } public void decrElement() { currentIndex--; loadCompositeData(elements[currentIndex]); } public boolean canDecrement() { if (isCompositeType && currentIndex > 0) { return true; } return false; } public boolean canIncrement() { if (isCompositeType && currentIndex < size - 1) { return true; } return false; } private void loadArray() { if (isCompositeType) { elements = (CompositeData[]) val; size = elements.length; if (size != 0) { loadCompositeData(elements[0]); } } else { load(); } } private void load() { Object[] rowData = new Object[1]; size = Array.getLength(val); for (int i = 0; i < size; i++) { rowData[0] = isTabularType ? new XTabularData(this, (TabularData) Array.get(val, i)) : Array.get(val, i); String str = rowData[0].toString(); if (str.length() > col1Width) { col1Width = str.length(); } ((DefaultTableModel) getModel()).addRow(rowData); } } @Override public String toString() { if (dimension > 1) { return Resources.getText("LBL_DimensionIsNotSupported") + // NOI18N ": " + dimension; // NOI18N } else { return elemType.getTypeName() + "[" + size + "]"; // NOI18N } } } /** * The supplied value is viewable iff: * - it's a CompositeData/TabularData, or * - it's a non-empty array of CompositeData/TabularData, or * - it's a non-empty Collection of CompositeData/TabularData. */ public static boolean isViewableValue(Object value) { // Check for CompositeData/TabularData // if (value instanceof CompositeData || value instanceof TabularData) { return true; } // Check for non-empty array of CompositeData/TabularData // if (value instanceof CompositeData[] || value instanceof TabularData[]) { return Array.getLength(value) > 0; } // Check for non-empty Collection of CompositeData/TabularData // if (value instanceof Collection) { Collection<?> c = (Collection<?>) value; if (c.isEmpty()) { // Empty Collections are not viewable // return false; } else { // Only Collections of CompositeData/TabularData are viewable // return Utils.isUniformCollection(c, CompositeData.class) || Utils.isUniformCollection(c, TabularData.class); } } return false; } public static Component loadOpenType(Object value) { Component comp = null; if(isViewableValue(value)) { XOpenTypeViewer open = new XOpenTypeViewer(value); comp = open; } return comp; } private XOpenTypeViewer(Object value) { XOpenTypeData comp = null; if (value instanceof CompositeData) { comp = new XCompositeData(null, (CompositeData) value); } else if (value instanceof TabularData) { comp = new XTabularData(null, (TabularData) value); } else if (value instanceof CompositeData[]) { CompositeData cda[] = (CompositeData[]) value; CompositeType ct = cda[0].getCompositeType(); comp = new XArrayData(null, 1, ct, cda); } else if (value instanceof TabularData[]) { TabularData tda[] = (TabularData[]) value; TabularType tt = tda[0].getTabularType(); comp = new XArrayData(null, 1, tt, tda); } else if (value instanceof Collection) { // At this point we know 'value' is a uniform collection, either // Collection<CompositeData> or Collection<TabularData>, because // isViewableValue() has been called before calling the private // XOpenTypeViewer() constructor. // Object e = ((Collection<?>) value).iterator().next(); if (e instanceof CompositeData) { Collection<?> cdc = (Collection<?>) value; CompositeData cda[] = cdc.toArray(new CompositeData[0]); CompositeType ct = cda[0].getCompositeType(); comp = new XArrayData(null, 1, ct, cda); } else if (e instanceof TabularData) { Collection<?> tdc = (Collection<?>) value; TabularData tda[] = tdc.toArray(new TabularData[0]); TabularType tt = tda[0].getTabularType(); comp = new XArrayData(null, 1, tt, tda); } } setupDisplay(comp); try { comp.viewed(this); } catch (Exception e) { // Nothing to change, the element can't be displayed LOGGER.log(Level.SEVERE, "Exception viewing openType", e); // NOI18N } } void setOpenType(XOpenTypeData data) { if (current != null) { current.removeMouseListener(listener); } current = data; // Enable/Disable the previous (<<) button if (current.getViewedParent() == null) { prev.setEnabled(false); } else { prev.setEnabled(true); } // Set the listener to handle double-click mouse events current.addMouseListener(listener); // Enable/Disable the tabular buttons if (!(data instanceof XViewedTabularData)) { tabularPrev.setEnabled(false); tabularNext.setEnabled(false); tabularLabel.setText(tabularNavigationSingle); tabularLabel.setEnabled(false); } else { XViewedTabularData tabular = (XViewedTabularData) data; tabularNext.setEnabled(tabular.canIncrement()); tabularPrev.setEnabled(tabular.canDecrement()); boolean hasMoreThanOneElement = tabular.canIncrement() || tabular.canDecrement(); if (hasMoreThanOneElement) { tabularLabel.setText( Resources.getText("LBL_MBeansTab.tabularNavigationMultiple", // NOI18N String.format("%d", tabular.getSelectedElementIndex() + 1), // NOI18N String.format("%d", tabular.getElementCount()))); // NOI18N } else { tabularLabel.setText(tabularNavigationSingle); } tabularLabel.setEnabled(hasMoreThanOneElement); } // Enable/Disable the composite buttons if (!(data instanceof XViewedArrayData)) { incr.setEnabled(false); decr.setEnabled(false); compositeLabel.setText(compositeNavigationSingle); compositeLabel.setEnabled(false); } else { XViewedArrayData array = (XViewedArrayData) data; incr.setEnabled(array.canIncrement()); decr.setEnabled(array.canDecrement()); boolean hasMoreThanOneElement = array.canIncrement() || array.canDecrement(); if (hasMoreThanOneElement) { compositeLabel.setText( Resources.getText("LBL_MBeansTab.compositeNavigationMultiple", // NOI18N String.format("%d", array.getSelectedElementIndex() + 1), // NOI18N String.format("%d", array.getElementCount()))); // NOI18N } else { compositeLabel.setText(compositeNavigationSingle); } compositeLabel.setEnabled(hasMoreThanOneElement); } container.invalidate(); container.setViewportView(current); container.validate(); } public void actionPerformed(ActionEvent event) { if (event.getSource() instanceof JButton) { JButton b = (JButton) event.getSource(); if (b == prev) { XOpenTypeData parent = current.getViewedParent(); try { parent.viewed(this); } catch (Exception e) { //Nothing to change, the element can't be displayed } } else if (b == incr) { ((XViewedArrayData) current).incrElement(); try { current.viewed(this); } catch (Exception e) { //Nothing to change, the element can't be displayed } } else if (b == decr) { ((XViewedArrayData) current).decrElement(); try { current.viewed(this); } catch (Exception e) { //Nothing to change, the element can't be displayed } } else if (b == tabularNext) { ((XViewedTabularData) current).incrElement(); try { current.viewed(this); } catch (Exception e) { //Nothing to change, the element can't be displayed } } else if (b == tabularPrev) { ((XViewedTabularData) current).decrElement(); try { current.viewed(this); } catch (Exception e) { //Nothing to change, the element can't be displayed } } } } private void setupDisplay(XOpenTypeData data) { setBackground(Color.white); container = new JScrollPane(data, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); JPanel buttons = new JPanel(new FlowLayout(FlowLayout.LEFT)); tabularPrev = new JButton(Resources.getText("LBL_<")); // NOI18N tabularNext = new JButton(Resources.getText("LBL_>")); // NOI18N JPanel tabularButtons = new JPanel(new FlowLayout(FlowLayout.LEFT)); tabularButtons.add(tabularPrev); tabularPrev.addActionListener(this); tabularLabel = new JLabel(tabularNavigationSingle); tabularLabel.setEnabled(false); tabularButtons.add(tabularLabel); tabularButtons.add(tabularNext); tabularNext.addActionListener(this); tabularButtons.setBackground(Color.white); prev = new JButton(Resources.getText("LBL_<<")); // NOI18N prev.addActionListener(this); buttons.add(prev); incr = new JButton(Resources.getText("LBL_>")); // NOI18N incr.addActionListener(this); decr = new JButton(Resources.getText("LBL_<")); // NOI18N decr.addActionListener(this); JPanel array = new JPanel(); array.setBackground(Color.white); array.add(decr); compositeLabel = new JLabel(compositeNavigationSingle); compositeLabel.setEnabled(false); array.add(compositeLabel); array.add(incr); buttons.add(array); setLayout(new BorderLayout()); buttons.setBackground(Color.white); JPanel navigationPanel = new JPanel(new BorderLayout()); navigationPanel.setBackground(Color.white); navigationPanel.add(tabularButtons, BorderLayout.NORTH); navigationPanel.add(buttons, BorderLayout.WEST); add(navigationPanel, BorderLayout.NORTH); add(container, BorderLayout.CENTER); Dimension d = new Dimension((int)container.getPreferredSize(). getWidth() + 20, (int)container.getPreferredSize(). getHeight() + 20); setPreferredSize(d); } }