View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2009 eviware.com 
3    *
4    *  soapUI is free software; you can redistribute it and/or modify it under the 
5    *  terms of version 2.1 of the GNU Lesser General Public License as published by 
6    *  the Free Software Foundation.
7    *
8    *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
9    *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
10   *  See the GNU Lesser General Public License for more details at gnu.org.
11   */
12  
13  package com.eviware.soapui.support.components;
14  
15  import java.awt.BorderLayout;
16  import java.awt.Color;
17  import java.awt.Component;
18  import java.awt.Font;
19  import java.awt.Toolkit;
20  import java.awt.datatransfer.StringSelection;
21  import java.awt.event.ActionEvent;
22  import java.beans.PropertyChangeEvent;
23  import java.beans.PropertyChangeListener;
24  import java.lang.reflect.InvocationTargetException;
25  import java.util.ArrayList;
26  import java.util.List;
27  
28  import javax.swing.AbstractAction;
29  import javax.swing.Action;
30  import javax.swing.BorderFactory;
31  import javax.swing.DefaultCellEditor;
32  import javax.swing.JComboBox;
33  import javax.swing.JComponent;
34  import javax.swing.JPanel;
35  import javax.swing.JPasswordField;
36  import javax.swing.JScrollPane;
37  import javax.swing.JTable;
38  import javax.swing.JTextField;
39  import javax.swing.TransferHandler;
40  import javax.swing.border.TitledBorder;
41  import javax.swing.table.AbstractTableModel;
42  import javax.swing.table.DefaultTableCellRenderer;
43  import javax.swing.table.TableCellEditor;
44  import javax.swing.table.TableModel;
45  
46  import org.apache.commons.beanutils.BeanUtils;
47  import org.apache.commons.beanutils.PropertyUtils;
48  
49  import com.eviware.soapui.SoapUI;
50  import com.eviware.soapui.support.PropertyChangeNotifier;
51  import com.eviware.soapui.support.StringUtils;
52  
53  /***
54   * Table for displaying property name/value pairs
55   * 
56   * @author Ole.Matzura
57   */
58  
59  public class JPropertiesTable<T> extends JPanel
60  {
61  	public final static Object[] BOOLEAN_OPTIONS = new Object[] { Boolean.TRUE, Boolean.FALSE };
62  
63  	private PropertiesTableModel<T> tableModel;
64  	private JTable table;
65  
66  	private TitledBorder titledBorder;
67  
68  	private String title;
69  
70  	public JPropertiesTable( String title )
71  	{
72  		this( title, null );
73  	}
74  
75  	public JPropertiesTable( String title, T propertyObject )
76  	{
77  		super( new BorderLayout() );
78  		this.title = title;
79  
80  		tableModel = new PropertiesTableModel<T>( propertyObject );
81  		table = new PTable( tableModel );
82  
83  		table.getColumnModel().getColumn( 0 ).setHeaderValue( "Property" );
84  		table.getColumnModel().getColumn( 1 ).setHeaderValue( "Value" );
85  		table.getColumnModel().getColumn( 0 ).setCellRenderer( new PropertiesTableCellRenderer() );
86  		table.getColumnModel().getColumn( 1 ).setCellRenderer( new PropertiesTableCellRenderer() );
87  
88  		add( new JScrollPane( table ), BorderLayout.CENTER );
89  		titledBorder = BorderFactory.createTitledBorder( BorderFactory.createEmptyBorder(), title );
90  		titledBorder.setTitleFont( titledBorder.getTitleFont().deriveFont( Font.PLAIN, 11 ) );
91  
92  		if( title != null )
93  			setBorder( titledBorder );
94  
95  		table.setBackground( Color.WHITE );
96  		setPreferredSize( table.getPreferredSize() );
97  	}
98  
99  	public void setTitle( String title )
100 	{
101 		this.title = title;
102 		titledBorder.setTitle( title );
103 		setBorder( titledBorder );
104 		repaint();
105 	}
106 
107 	public String getTitle()
108 	{
109 		return title;
110 	}
111 
112 	@Override
113 	public void removeNotify()
114 	{
115 		getTableModel().release();
116 		super.removeNotify();
117 	}
118 
119 	@Override
120 	public void addNotify()
121 	{
122 		getTableModel().attach();
123 		super.addNotify();
124 	}
125 
126 	public void setPropertyObject( T propertyObject )
127 	{
128 		if( table.isEditing() )
129 			table.getCellEditor().stopCellEditing();
130 
131 		tableModel.setPropertyObject( propertyObject );
132 	}
133 
134 	public PropertiesTableModel<?> getTableModel()
135 	{
136 		return tableModel;
137 	}
138 
139 	public PropertyDescriptor addProperty( String caption, String name )
140 	{
141 		return addProperty( caption, name, false );
142 	}
143 
144 	public PropertyDescriptor addProperty( String caption, String name, boolean editable )
145 	{
146 		return addProperty( caption, name, editable, null );
147 	}
148 
149 	public PropertyDescriptor addProperty( String caption, String name, boolean editable, PropertyFormatter formatter )
150 	{
151 		return tableModel.addProperty( caption, name, editable, formatter );
152 	}
153 
154 	public static final class PropertiesTableModel<T> extends AbstractTableModel implements PropertyChangeListener
155 	{
156 		private List<PropertyDescriptor> properties = new ArrayList<PropertyDescriptor>();
157 		private T propertyObject;
158 		private boolean attached;
159 
160 		public PropertiesTableModel( T propertyObject )
161 		{
162 			this.propertyObject = propertyObject;
163 		}
164 
165 		public void attach()
166 		{
167 			if( !attached && propertyObject instanceof PropertyChangeNotifier )
168 			{
169 				( ( PropertyChangeNotifier )propertyObject ).addPropertyChangeListener( this );
170 				attached = true;
171 			}
172 		}
173 
174 		public void setPropertyObject( T propertyObject )
175 		{
176 			release();
177 			this.propertyObject = propertyObject;
178 			attach();
179 			fireTableDataChanged();
180 		}
181 
182 		public PropertyDescriptor addProperty( String caption, String name, boolean editable, PropertyFormatter formatter )
183 		{
184 			PropertyDescriptor propertyDescriptor = new PropertyDescriptor( caption, name, editable, formatter );
185 			properties.add( propertyDescriptor );
186 			return propertyDescriptor;
187 		}
188 
189 		public PropertyDescriptor addProperty( String caption, String name, Object[] options )
190 		{
191 			PropertyDescriptor propertyDescriptor = new PropertyDescriptor( caption, name, options );
192 			properties.add( propertyDescriptor );
193 			return propertyDescriptor;
194 		}
195 
196 		public int getRowCount()
197 		{
198 			return properties.size();
199 		}
200 
201 		public int getColumnCount()
202 		{
203 			return 2;
204 		}
205 
206 		public boolean isCellEditable( int rowIndex, int columnIndex )
207 		{
208 			if( columnIndex == 0 || propertyObject == null )
209 				return false;
210 			return properties.get( rowIndex ).isEditable()
211 					&& PropertyUtils.isWriteable( propertyObject, properties.get( rowIndex ).getName() );
212 		}
213 
214 		public void setValueAt( Object aValue, int rowIndex, int columnIndex )
215 		{
216 			try
217 			{
218 				if( propertyObject != null && columnIndex == 1 && properties.get( rowIndex ).isEditable() )
219 				{
220 					BeanUtils.setProperty( propertyObject, properties.get( rowIndex ).getName(), aValue );
221 				}
222 			}
223 			catch( IllegalAccessException e )
224 			{
225 				SoapUI.logError( e );
226 			}
227 			catch( InvocationTargetException e )
228 			{
229 				SoapUI.logError( e );
230 			}
231 		}
232 
233 		public Object getValueAt( int rowIndex, int columnIndex )
234 		{
235 			if( propertyObject == null )
236 				return null;
237 
238 			try
239 			{
240 				PropertyDescriptor propertyDescriptor = properties.get( rowIndex );
241 				switch( columnIndex )
242 				{
243 				case 0 :
244 					return propertyDescriptor.getCaption();
245 				case 1 :
246 				{
247 					Object value = PropertyUtils.getSimpleProperty( propertyObject, propertyDescriptor.getName() );
248 					return propertyDescriptor.getFormatter().format( propertyDescriptor.getName(), value );
249 				}
250 				}
251 			}
252 			catch( IllegalAccessException e )
253 			{
254 				SoapUI.logError( e );
255 			}
256 			catch( InvocationTargetException e )
257 			{
258 				SoapUI.logError( e );
259 			}
260 			catch( NoSuchMethodException e )
261 			{
262 				SoapUI.logError( e );
263 			}
264 
265 			return null;
266 		}
267 
268 		public PropertyDescriptor getPropertyDescriptorAt( int row )
269 		{
270 			return properties.get( row );
271 		}
272 
273 		public void propertyChange( PropertyChangeEvent evt )
274 		{
275 			fireTableDataChanged();
276 		}
277 
278 		public void release()
279 		{
280 			if( propertyObject instanceof PropertyChangeNotifier && attached )
281 			{
282 				( ( PropertyChangeNotifier )propertyObject ).removePropertyChangeListener( this );
283 				attached = false;
284 			}
285 		}
286 
287 		public PropertyDescriptor addPropertyShadow( String caption, String name, boolean editable )
288 		{
289 			PropertyDescriptor propertyDescriptor = new PropertyDescriptor( caption, name, editable );
290 			properties.add( propertyDescriptor );
291 			return propertyDescriptor;
292 		}
293 	}
294 
295 	public static class PropertyDescriptor
296 	{
297 		private final String caption;
298 		private final String name;
299 		private boolean editable;
300 		private PropertyFormatter formatter;
301 		private Object[] options;
302 		private DefaultCellEditor cellEditor;
303 		private String description;
304 
305 		public PropertyDescriptor( String caption, String name, boolean editable, PropertyFormatter formatter )
306 		{
307 			this.caption = caption;
308 			this.name = name;
309 			this.editable = editable;
310 			this.formatter = formatter;
311 
312 			JTextField textField = new JTextField();
313 			textField.setBorder( BorderFactory.createEmptyBorder() );
314 			cellEditor = new DefaultCellEditor( textField );
315 		}
316 
317 		public PropertyDescriptor( String caption, String name, Object[] options )
318 		{
319 			this.caption = caption;
320 			this.name = name;
321 			this.options = options;
322 			editable = true;
323 
324 			JComboBox comboBox = new JComboBox( options );
325 
326 			if( options.length > 0 && options[0] == null )
327 			{
328 				comboBox.setEditable( true );
329 				comboBox.removeItemAt( 0 );
330 			}
331 
332 			comboBox.setBorder( null );
333 			cellEditor = new DefaultCellEditor( comboBox );
334 		}
335 
336 		/***
337 		 * For password field in table.
338 		 * 
339 		 * @author robert nemet
340 		 * @param caption
341 		 * @param name
342 		 * @param editable
343 		 */
344 		public PropertyDescriptor( String caption, String name, boolean editable )
345 		{
346 
347 			this.caption = caption;
348 			this.name = name;
349 			this.editable = editable;
350 
351 			JPasswordField textField = new JPasswordField();
352 			textField.setBorder( BorderFactory.createEmptyBorder() );
353 			cellEditor = new DefaultCellEditor( textField );
354 		}
355 
356 		public void setFormatter( PropertyFormatter formatter )
357 		{
358 			this.formatter = formatter;
359 		}
360 
361 		public PropertyFormatter getFormatter()
362 		{
363 			return formatter == null ? DefaultFormatter.getInstance() : formatter;
364 		}
365 
366 		public String getCaption()
367 		{
368 			return caption;
369 		}
370 
371 		public String getDescription()
372 		{
373 			return description;
374 		}
375 
376 		public void setDescription( String description )
377 		{
378 			this.description = description;
379 		}
380 
381 		public boolean isEditable()
382 		{
383 			return editable;
384 		}
385 
386 		public Object[] getOptions()
387 		{
388 			return options;
389 		}
390 
391 		public boolean hasOptions()
392 		{
393 			return options != null;
394 		}
395 
396 		public String getName()
397 		{
398 			return name;
399 		}
400 
401 		public TableCellEditor getCellEditor()
402 		{
403 			return cellEditor;
404 		}
405 	}
406 
407 	private static class PropertiesTableCellRenderer extends DefaultTableCellRenderer
408 	{
409 		public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus,
410 				int row, int column )
411 		{
412 			Component component;
413 			DefaultCellEditor cellEditor = ( DefaultCellEditor )table.getCellEditor( row, column );
414 			if( cellEditor.getComponent() instanceof JPasswordField && value instanceof String )
415 			{
416 				if( value != null && ( ( String )value ).length() > 0 )
417 				{
418 					component = super.getTableCellRendererComponent( table, "**************", isSelected, hasFocus, row,
419 							column );
420 				}
421 				else
422 				{
423 					component = super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column );
424 				}
425 			}
426 			else
427 			{
428 				component = super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column );
429 			}
430 			if( component instanceof JComponent )
431 			{
432 				PropertyDescriptor descriptor = ( ( PropertiesTableModel<?> )table.getModel() )
433 						.getPropertyDescriptorAt( row );
434 
435 				if( StringUtils.hasContent( descriptor.getDescription() ) )
436 				{
437 					( ( JComponent )component ).setToolTipText( descriptor.getDescription() );
438 				}
439 				// do not set tooltip as value for password field, it has no sense.
440 				else if( value != null && StringUtils.hasContent( value.toString() )
441 						&& !( cellEditor.getComponent() instanceof JPasswordField ) )
442 				{
443 					( ( JComponent )component ).setToolTipText( value.toString() );
444 				}
445 				else
446 				{
447 					( ( JComponent )component ).setToolTipText( null );
448 				}
449 			}
450 
451 			return component;
452 		}
453 	}
454 
455 	/*
456 	 * defaultcelleditor private class PropertiesTableCellEditor extends
457 	 * AbstractCellEditor implements TableCellEditor { private JTextField
458 	 * textField; private JComboBox comboBox; private JComponent current;
459 	 * 
460 	 * public PropertiesTableCellEditor() { textField = new JTextField();
461 	 * comboBox = new JComboBox();
462 	 * comboBox.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE); }
463 	 * 
464 	 * public Component getTableCellEditorComponent(JTable table, Object value,
465 	 * boolean isSelected, int row, int column) { PropertyDescriptor descriptor =
466 	 * tableModel.getPropertyDescriptorAt( row );
467 	 * 
468 	 * if( descriptor.hasOptions()) { comboBox.setModel( new
469 	 * DefaultComboBoxModel( descriptor.getOptions() ));
470 	 * comboBox.setSelectedItem( value ); current = comboBox; } else {
471 	 * textField.setText( value == null ? "" : value.toString() ); current =
472 	 * textField; }
473 	 * 
474 	 * current.setBorder( null ); current.setBackground( Color.WHITE );
475 	 * 
476 	 * return current; }
477 	 * 
478 	 * public Object getCellEditorValue() { return current == comboBox ?
479 	 * comboBox.getSelectedItem() : textField.getText(); } }
480 	 */
481 
482 	/***
483 	 * Formatter used for displaying property values
484 	 * 
485 	 * @author Ole.Matzura
486 	 */
487 
488 	public interface PropertyFormatter
489 	{
490 		public Object format( String propertyName, Object value );
491 	}
492 
493 	private static class DefaultFormatter implements PropertyFormatter
494 	{
495 		private static PropertyFormatter instance;
496 
497 		public static PropertyFormatter getInstance()
498 		{
499 			if( instance == null )
500 				instance = new DefaultFormatter();
501 
502 			return instance;
503 		}
504 
505 		public Object format( String propertyName, Object value )
506 		{
507 			return value;
508 		}
509 	}
510 
511 	public PropertyDescriptor addProperty( String caption, String name, Object[] options )
512 	{
513 		return tableModel.addProperty( caption, name, options );
514 	}
515 
516 	private class PTable extends JTable
517 	{
518 		public PTable( TableModel tableModel )
519 		{
520 			super( tableModel );
521 
522 			// setAutoStartEditOnKeyStroke( true );
523 
524 			getActionMap().put( TransferHandler.getCopyAction().getValue( Action.NAME ), new AbstractAction()
525 			{
526 				public void actionPerformed( ActionEvent e )
527 				{
528 					int row = getSelectedRow();
529 					if( row == -1 )
530 						return;
531 
532 					StringSelection selection = new StringSelection( getValueAt( row, 1 ).toString() );
533 					Toolkit.getDefaultToolkit().getSystemClipboard().setContents( selection, selection );
534 				}
535 			} );
536 
537 			putClientProperty( "terminateEditOnFocusLost", Boolean.TRUE );
538 			/*
539 			 * addFocusListener( new FocusAdapter() {
540 			 * 
541 			 * public void focusLost(FocusEvent e) { if( isEditing() &&
542 			 * getCellEditor() != null ) getCellEditor().stopCellEditing(); }} );
543 			 */
544 		}
545 
546 		public TableCellEditor getCellEditor( int row, int column )
547 		{
548 			if( column == 0 )
549 				return super.getCellEditor( row, column );
550 			else
551 				return tableModel.getPropertyDescriptorAt( row ).getCellEditor();
552 		}
553 	}
554 
555 	/***
556 	 * Value in this field will not be showen. It will be masked...
557 	 * 
558 	 * @author robert nemet
559 	 * @param caption
560 	 * @param name
561 	 * @param editable
562 	 * @return
563 	 */
564 	public PropertyDescriptor addPropertyShadow( String caption, String name, boolean editable )
565 	{
566 		return tableModel.addPropertyShadow( caption, name, editable );
567 	}
568 }