View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2008 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 ) return false;
209 			return properties.get( rowIndex ).isEditable() && 
210 				PropertyUtils.isWriteable( propertyObject, properties.get( rowIndex ).getName() );
211 		}
212 		
213 		public void setValueAt(Object aValue, int rowIndex, int columnIndex)
214 		{
215 			try
216 			{
217 				if( propertyObject != null && columnIndex == 1 && properties.get( rowIndex ).isEditable() )
218 				{
219 					BeanUtils.setProperty( propertyObject, properties.get( rowIndex ).getName(), aValue );
220 				}
221 			}
222 			catch (IllegalAccessException e)
223 			{
224 				SoapUI.logError( e );
225 			}
226 			catch (InvocationTargetException e)
227 			{
228 				SoapUI.logError( e );
229 			}
230 		}
231 
232 		public Object getValueAt(int rowIndex, int columnIndex)
233 		{
234 			if( propertyObject == null )
235 				return null;
236 			
237 			try
238 			{
239 				PropertyDescriptor propertyDescriptor = properties.get(rowIndex);
240 				switch (columnIndex)
241 				{
242 					case 0:
243 						return propertyDescriptor.getCaption();
244 					case 1:
245 					{
246 						Object value = PropertyUtils.getSimpleProperty(propertyObject,propertyDescriptor.getName());
247 						return propertyDescriptor.getFormatter().format( propertyDescriptor.getName(), value );
248 					}
249 				}
250 			}
251 			catch (IllegalAccessException e)
252 			{
253 				SoapUI.logError( e );
254 			}
255 			catch (InvocationTargetException e)
256 			{
257 				SoapUI.logError( e );
258 			}
259 			catch (NoSuchMethodException e)
260 			{
261 				SoapUI.logError( e );
262 			}
263 			
264 			return null;
265 		}
266 		
267 		public PropertyDescriptor getPropertyDescriptorAt( int row )
268 		{
269 			return properties.get( row );
270 		}
271 
272 		public void propertyChange(PropertyChangeEvent evt)
273 		{
274 			fireTableDataChanged();
275 		}
276 
277 		public void release()
278 		{
279 			if( propertyObject instanceof PropertyChangeNotifier && attached )
280 			{
281 				((PropertyChangeNotifier)propertyObject).removePropertyChangeListener( this );
282 				attached = false;
283 			}
284 		}
285 
286 		public PropertyDescriptor addPropertyShadow(String caption,	String name, boolean editable) {
287 			PropertyDescriptor propertyDescriptor = new PropertyDescriptor( caption, name, editable );
288 			properties.add( propertyDescriptor);
289 			return propertyDescriptor;
290 		}}
291 	
292 	public static class PropertyDescriptor
293 	{
294 		private final String caption;
295 		private final String name;
296 		private boolean editable;
297 		private PropertyFormatter formatter ;
298 		private Object [] options;
299 		private DefaultCellEditor cellEditor;
300 		private String description;
301 
302 		public PropertyDescriptor(String caption, String name, boolean editable, PropertyFormatter formatter)
303 		{
304 			this.caption = caption;
305 			this.name = name;
306 			this.editable = editable;
307 			this.formatter = formatter;
308 			
309 			JTextField textField = new JTextField();
310 			textField.setBorder(BorderFactory.createEmptyBorder()); 
311 			cellEditor = new DefaultCellEditor( textField );
312 		}
313 
314 		public PropertyDescriptor(String caption, String name, Object[] options)
315 		{
316 			this.caption = caption;
317 			this.name = name;
318 			this.options = options;
319 			editable = true;
320 			
321 			JComboBox comboBox = new JComboBox( options );
322 			
323 			if( options.length > 0 && options[0] == null )
324 			{
325 				comboBox.setEditable( true );
326 				comboBox.removeItemAt( 0 );
327 			}
328 			
329 			comboBox.setBorder(null); 
330 			cellEditor = new DefaultCellEditor( comboBox );
331 		}
332 
333 		
334 		/***
335 		 * For password field in table.
336 		 * 
337 		 * @author robert nemet
338 		 * @param caption
339 		 * @param name
340 		 * @param editable
341 		 */
342 		public PropertyDescriptor(String caption, String name, boolean editable ) {
343 			
344 			this.caption = caption;
345 			this.name = name;
346 			this.editable = editable;
347 			
348 			JPasswordField textField = new JPasswordField();
349 			textField.setBorder(BorderFactory.createEmptyBorder()); 
350 			cellEditor = new DefaultCellEditor( textField );
351 		}
352 
353 		public void setFormatter( PropertyFormatter formatter )
354 		{
355 			this.formatter = formatter;
356 		}
357 		
358 		public PropertyFormatter getFormatter()
359 		{
360 			return formatter == null ? DefaultFormatter.getInstance() : formatter;
361 		}
362 
363 		public String getCaption()
364 		{
365 			return caption;
366 		}
367 		
368 		public String getDescription()
369 		{
370 			return description;
371 		}
372 
373 		public void setDescription( String description )
374 		{
375 			this.description = description;
376 		}
377 
378 		public boolean isEditable()
379 		{
380 			return editable;
381 		}
382 		
383 		public Object[] getOptions()
384 		{
385 			return options;
386 		}
387 		
388 		public boolean hasOptions()
389 		{
390 			return options != null;
391 		}
392 
393 		public String getName()
394 		{
395 			return name;
396 		}
397 		
398 		public TableCellEditor getCellEditor()
399 		{
400 			return cellEditor;
401 		}
402 	}
403 
404 	private static class PropertiesTableCellRenderer extends DefaultTableCellRenderer
405 	{
406 		public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
407 		{
408 			Component component;
409 			DefaultCellEditor cellEditor = (DefaultCellEditor) table.getCellEditor(row, column);
410 			if( cellEditor.getComponent() instanceof JPasswordField && value instanceof String) { 
411 				if ( value != null && ((String)value).length() > 0 ) {
412 					component = super.getTableCellRendererComponent(table, "**************", isSelected, hasFocus,
413 						row, column);
414 				} else {
415 					component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus,	row, column);
416 				}
417 			} else {
418 				component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus,
419 					row, column);
420 			}
421 			if( component instanceof JComponent )
422 			{
423 				PropertyDescriptor descriptor = ((PropertiesTableModel<?>)table.getModel()).getPropertyDescriptorAt( row );
424 				
425 				if( StringUtils.hasContent( descriptor.getDescription()  ))
426 				{
427 					((JComponent)component).setToolTipText( descriptor.getDescription() );
428 				}
429 				// do not set tooltip as value for password field, it has no sense.
430 				else if( value != null && StringUtils.hasContent( value.toString() ) && !(cellEditor.getComponent() instanceof JPasswordField))
431 				{
432 					((JComponent)component).setToolTipText( value.toString() );
433 				}
434 				else
435 				{
436 					((JComponent)component).setToolTipText( null );
437 				}
438 			}
439 			
440 			return component;
441 		}
442 	}
443 	/*defaultcelleditor
444 	private class PropertiesTableCellEditor extends AbstractCellEditor implements TableCellEditor
445 	{
446 		private JTextField textField;
447 		private JComboBox comboBox;
448 		private JComponent current;
449 		
450 		public PropertiesTableCellEditor()
451 		{
452 			textField = new JTextField();
453 			comboBox = new JComboBox();
454 			comboBox.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
455 		}
456 
457 		public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column)
458 		{
459 			PropertyDescriptor descriptor = tableModel.getPropertyDescriptorAt( row );
460 			
461 			if( descriptor.hasOptions())
462 			{
463 				comboBox.setModel( new DefaultComboBoxModel( descriptor.getOptions() ));
464 				comboBox.setSelectedItem( value );
465 				current = comboBox;
466 			}
467 			else
468 			{
469 				textField.setText( value == null ? "" : value.toString() );
470 				current = textField;
471 			}
472 			
473 			current.setBorder( null );
474 			current.setBackground( Color.WHITE );
475 			
476 			return current;
477 		}
478 
479 		public Object getCellEditorValue()
480 		{
481 			return current == comboBox ? comboBox.getSelectedItem() : textField.getText();
482 		}
483 	}
484 	*/
485 	
486 	/***
487 	 * Formatter used for displaying property values
488 	 * 
489 	 * @author Ole.Matzura
490 	 */
491 	
492 	public interface PropertyFormatter
493 	{
494 		public Object format( String propertyName, Object value );
495 	}
496 
497 	private static class DefaultFormatter implements PropertyFormatter
498 	{
499 		private static PropertyFormatter instance;
500 
501 		public static PropertyFormatter getInstance()
502 		{
503 			if( instance == null )
504 				instance = new DefaultFormatter();
505 			
506 			return instance;
507 		}
508 
509 		public Object format(String propertyName, Object value)
510 		{
511 			return value;
512 		}
513 	}
514 
515 	public PropertyDescriptor addProperty(String caption, String name, Object [] options)
516 	{
517 		return tableModel.addProperty( caption, name, options );
518 	}
519 	
520 	private class PTable extends JTable
521 	{
522 		public PTable( TableModel tableModel )
523 		{
524 			super( tableModel );
525 			
526 		//	setAutoStartEditOnKeyStroke( true );
527 			
528 			getActionMap().put( TransferHandler.getCopyAction().getValue(Action.NAME), new AbstractAction()  
529 			{
530 				public void actionPerformed(ActionEvent e)
531 				{
532 					int row = getSelectedRow();
533 					if( row == -1 )
534 						return;
535 					
536 					StringSelection selection = new StringSelection( getValueAt( row, 1 ).toString() ); 
537 					Toolkit.getDefaultToolkit().getSystemClipboard().setContents( selection, selection );
538 				}});
539 			
540 			putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
541 		/*	
542 			addFocusListener( new FocusAdapter() {
543 
544 				public void focusLost(FocusEvent e)
545 				{
546 					if( isEditing() && getCellEditor() != null )
547 						getCellEditor().stopCellEditing();
548 				}}  );*/
549 		}
550 
551 		public TableCellEditor getCellEditor(int row, int column)
552 		{
553 			if( column == 0 )
554 				return super.getCellEditor(row, column);
555 			else
556 				return tableModel.getPropertyDescriptorAt( row ).getCellEditor();
557 		}
558 	}
559 
560 	/***
561 	 * Value in this field will not be showen. It will be masked...
562 	 * 
563 	 * @author robert nemet
564 	 * @param caption
565 	 * @param name
566 	 * @param editable
567 	 * @return
568 	 */
569 	public PropertyDescriptor addPropertyShadow( String caption, String name, boolean editable )
570 	{
571 		return tableModel.addPropertyShadow( caption, name, editable );
572 	}
573 }