/*
* SoapUI, Copyright (C) 2004-2016 SmartBear Software
*
* Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent
* versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* http://ec.europa.eu/idabc/eupl
*
* Unless required by applicable law or agreed to in writing, software distributed under the Licence is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the Licence for the specific language governing permissions and limitations
* under the Licence.
*/
package com.eviware.soapui.support.components;
import com.eviware.soapui.SoapUI;
import com.eviware.soapui.support.PropertyChangeNotifier;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.swing.JTableFactory;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.TransferHandler;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.border.TitledBorder;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
/**
* Table for displaying property name/value pairs
*
* @author Ole.Matzura
*/
@SuppressWarnings("serial")
public class JPropertiesTable<T> extends JPanel {
public final static Object[] BOOLEAN_OPTIONS = new Object[]{Boolean.TRUE, Boolean.FALSE};
private PropertiesTableModel<T> tableModel;
private JTable table;
private TitledBorder titledBorder;
private String title;
public JPropertiesTable(String title) {
this(title, null);
}
public JPropertiesTable(String title, T propertyObject) {
super(new BorderLayout());
this.title = title;
setBackground(Color.WHITE);
tableModel = new PropertiesTableModel<T>(propertyObject);
table = new PTable(tableModel);
table.setBackground(Color.WHITE);
table.getColumnModel().getColumn(0).setHeaderValue("Property");
table.getColumnModel().getColumn(1).setHeaderValue("Value");
table.getColumnModel().getColumn(0).setCellRenderer(new PropertiesTableCellRenderer());
table.getColumnModel().getColumn(1).setCellRenderer(new PropertiesTableCellRenderer());
add(new JScrollPane(table), BorderLayout.CENTER);
titledBorder = BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(), title);
/*
* Java 7 issue
*
* old: titledBorder.setTitleFont( titledBorder.getTitleFont().deriveFont(
* Font.PLAIN, 11 ) ); titledBorder.getTitleFont() return null in Java 7
*/
Font defaultUIFont = getUIDefaultFont();
if (defaultUIFont != null) {
titledBorder.setTitleFont(getUIDefaultFont().deriveFont(Font.PLAIN, 11));
}
if (title != null) {
setBorder(titledBorder);
}
table.setBackground(Color.WHITE);
setPreferredSize(table.getPreferredSize());
}
private Font getUIDefaultFont() {
UIDefaults uidefs = UIManager.getLookAndFeelDefaults();
for (Object key : uidefs.keySet()) {
if (uidefs.get(key) instanceof Font) {
return uidefs.getFont(key);
}
}
return null;
}
public void setTitle(String title) {
this.title = title;
titledBorder.setTitle(title);
setBorder(titledBorder);
repaint();
}
public String getTitle() {
return title;
}
@Override
public void removeNotify() {
getTableModel().release();
super.removeNotify();
}
@Override
public void addNotify() {
getTableModel().attach();
super.addNotify();
}
public void setPropertyObject(T propertyObject) {
if (table.isEditing()) {
table.getCellEditor().stopCellEditing();
}
tableModel.setPropertyObject(propertyObject);
}
public PropertiesTableModel<?> getTableModel() {
return tableModel;
}
public PropertyDescriptor addProperty(String caption, String name) {
return addProperty(caption, name, false);
}
public PropertyDescriptor addProperty(String caption, String name, boolean editable) {
return addProperty(caption, name, editable, null);
}
public PropertyDescriptor addProperty(String caption, String name, boolean editable, PropertyFormatter formatter) {
return tableModel.addProperty(caption, name, editable, formatter);
}
public static final class PropertiesTableModel<T> extends AbstractTableModel implements PropertyChangeListener {
private List<PropertyDescriptor> properties = new ArrayList<PropertyDescriptor>();
private T propertyObject;
private boolean attached;
public PropertiesTableModel(T propertyObject) {
this.propertyObject = propertyObject;
}
public void attach() {
if (!attached && propertyObject instanceof PropertyChangeNotifier) {
((PropertyChangeNotifier) propertyObject).addPropertyChangeListener(this);
attached = true;
}
}
public void setPropertyObject(T propertyObject) {
release();
this.propertyObject = propertyObject;
attach();
fireTableDataChanged();
}
public PropertyDescriptor addProperty(String caption, String name, boolean editable, PropertyFormatter formatter) {
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(caption, name, editable, formatter);
properties.add(propertyDescriptor);
return propertyDescriptor;
}
public PropertyDescriptor addProperty(String caption, String name, Object[] options) {
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(caption, name, options);
properties.add(propertyDescriptor);
return propertyDescriptor;
}
public int getRowCount() {
return properties.size();
}
public int getColumnCount() {
return 2;
}
public boolean isCellEditable(int rowIndex, int columnIndex) {
if (columnIndex == 0 || propertyObject == null) {
return false;
}
return properties.get(rowIndex).isEditable()
&& PropertyUtils.isWriteable(propertyObject, properties.get(rowIndex).getName());
}
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
try {
if (propertyObject != null && columnIndex == 1 && properties.get(rowIndex).isEditable()) {
BeanUtils.setProperty(propertyObject, properties.get(rowIndex).getName(), aValue);
}
} catch (IllegalAccessException e) {
SoapUI.logError(e);
} catch (InvocationTargetException e) {
SoapUI.logError(e);
}
}
public Object getValueAt(int rowIndex, int columnIndex) {
if (propertyObject == null) {
return null;
}
try {
PropertyDescriptor propertyDescriptor = properties.get(rowIndex);
switch (columnIndex) {
case 0:
return propertyDescriptor.getCaption();
case 1: {
Object value = PropertyUtils.getSimpleProperty(propertyObject, propertyDescriptor.getName());
return propertyDescriptor.getFormatter().format(propertyDescriptor.getName(), value);
}
}
} catch (IllegalAccessException e) {
SoapUI.logError(e);
} catch (InvocationTargetException e) {
SoapUI.logError(e);
} catch (NoSuchMethodException e) {
SoapUI.logError(e);
}
return null;
}
public PropertyDescriptor getPropertyDescriptorAt(int row) {
return properties.get(row);
}
public void propertyChange(PropertyChangeEvent evt) {
fireTableDataChanged();
}
public void release() {
if (propertyObject instanceof PropertyChangeNotifier && attached) {
((PropertyChangeNotifier) propertyObject).removePropertyChangeListener(this);
attached = false;
}
}
public PropertyDescriptor addPropertyShadow(String caption, String name, boolean editable) {
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(caption, name, editable);
properties.add(propertyDescriptor);
return propertyDescriptor;
}
}
public static class PropertyDescriptor {
private final String caption;
private final String name;
private boolean editable;
private PropertyFormatter formatter;
private Object[] options;
private DefaultCellEditor cellEditor;
private String description;
public PropertyDescriptor(String caption, String name, boolean editable, PropertyFormatter formatter) {
this.caption = caption;
this.name = name;
this.editable = editable;
this.formatter = formatter;
JTextField textField = new JTextField();
textField.setBorder(BorderFactory.createEmptyBorder());
cellEditor = new DefaultCellEditor(textField);
}
public PropertyDescriptor(String caption, String name, Object[] options) {
this.caption = caption;
this.name = name;
this.options = options;
editable = true;
JComboBox comboBox = new JComboBox(options);
if (options.length > 0 && options[0] == null) {
comboBox.setEditable(true);
comboBox.removeItemAt(0);
}
comboBox.setBorder(null);
cellEditor = new DefaultCellEditor(comboBox);
}
/**
* For password field in table.
*
* @param caption
* @param name
* @param editable
* @author robert nemet
*/
public PropertyDescriptor(String caption, String name, boolean editable) {
this.caption = caption;
this.name = name;
this.editable = editable;
JPasswordField textField = new JPasswordField();
textField.setBorder(BorderFactory.createEmptyBorder());
cellEditor = new DefaultCellEditor(textField);
}
public void setFormatter(PropertyFormatter formatter) {
this.formatter = formatter;
}
public PropertyFormatter getFormatter() {
return formatter == null ? DefaultFormatter.getInstance() : formatter;
}
public String getCaption() {
return caption;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public boolean isEditable() {
return editable;
}
public Object[] getOptions() {
return options;
}
public boolean hasOptions() {
return options != null;
}
public String getName() {
return name;
}
public TableCellEditor getCellEditor() {
return cellEditor;
}
}
private static class PropertiesTableCellRenderer extends DefaultTableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
int row, int column) {
Component component;
DefaultCellEditor cellEditor = (DefaultCellEditor) table.getCellEditor(row, column);
if (cellEditor.getComponent() instanceof JPasswordField && value instanceof String) {
if (value != null && ((String) value).length() > 0) {
component = super.getTableCellRendererComponent(table, "**************", isSelected, hasFocus, row,
column);
} else {
component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
}
} else {
component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
}
if (component instanceof JComponent) {
PropertyDescriptor descriptor = ((PropertiesTableModel<?>) table.getModel())
.getPropertyDescriptorAt(row);
if (StringUtils.hasContent(descriptor.getDescription())) {
((JComponent) component).setToolTipText(descriptor.getDescription());
}
// do not set tooltip as value for password field, it has no sense.
else if (value != null && StringUtils.hasContent(value.toString())
&& !(cellEditor.getComponent() instanceof JPasswordField)) {
((JComponent) component).setToolTipText(value.toString());
} else {
((JComponent) component).setToolTipText(null);
}
}
return component;
}
}
/**
* Formatter used for displaying property values
*
* @author Ole.Matzura
*/
public interface PropertyFormatter {
public Object format(String propertyName, Object value);
}
private static class DefaultFormatter implements PropertyFormatter {
private static PropertyFormatter instance;
public static PropertyFormatter getInstance() {
if (instance == null) {
instance = new DefaultFormatter();
}
return instance;
}
public Object format(String propertyName, Object value) {
return value;
}
}
public PropertyDescriptor addProperty(String caption, String name, Object[] options) {
return tableModel.addProperty(caption, name, options);
}
private class PTable extends JTable {
public PTable(TableModel tableModel) {
super(tableModel);
getActionMap().put(TransferHandler.getCopyAction().getValue(Action.NAME), new AbstractAction() {
public void actionPerformed(ActionEvent e) {
int row = getSelectedRow();
if (row == -1) {
return;
}
StringSelection selection = new StringSelection(getValueAt(row, 1).toString());
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, selection);
}
});
putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
if (UISupport.isMac()) {
setShowGrid(false);
setIntercellSpacing(new Dimension(0, 0));
}
}
@Override
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Component defaultRenderer = super.prepareRenderer(renderer, row, column);
if (UISupport.isMac()) {
JTableFactory.applyStripesToRenderer(row, defaultRenderer);
}
return defaultRenderer;
}
@Override
public boolean getShowVerticalLines() {
return !UISupport.isMac();
}
public TableCellEditor getCellEditor(int row, int column) {
if (column == 0) {
return super.getCellEditor(row, column);
} else {
return tableModel.getPropertyDescriptorAt(row).getCellEditor();
}
}
}
/**
* Value in this field will not be showen. It will be masked...
*
* @param caption
* @param name
* @param editable
* @return
* @author robert nemet
*/
public PropertyDescriptor addPropertyShadow(String caption, String name, boolean editable) {
return tableModel.addPropertyShadow(caption, name, editable);
}
}