/*
 *  soapUI, copyright (C) 2004-2012 smartbear.com 
 *
 *  soapUI is free software; you can redistribute it and/or modify it under the 
 *  terms of version 2.1 of the GNU Lesser General Public License as published by 
 *  the Free Software Foundation.
 *
 *  soapUI 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 Lesser General Public License for more details at gnu.org.
 */

package com.eviware.soapui.impl.wsdl.panels.teststeps;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JToggleButton;
import javax.swing.ListSelectionModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.text.Document;

import org.jdesktop.swingx.JXTable;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.impl.support.actions.ShowOnlineHelpAction;
import com.eviware.soapui.impl.wsdl.panels.support.MockTestRunContext;
import com.eviware.soapui.impl.wsdl.panels.support.MockTestRunner;
import com.eviware.soapui.impl.wsdl.panels.support.TestRunComponentEnabler;
import com.eviware.soapui.impl.wsdl.support.HelpUrls;
import com.eviware.soapui.impl.wsdl.teststeps.PropertyTransfer;
import com.eviware.soapui.impl.wsdl.teststeps.PropertyTransfersTestStep;
import com.eviware.soapui.impl.wsdl.teststeps.PropertyTransfersTestStep.PropertyTransferResult;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlTestRequest;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlTestRequestStep;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlTestStep;
import com.eviware.soapui.model.ModelItem;
import com.eviware.soapui.model.TestModelItem;
import com.eviware.soapui.model.TestPropertyHolder;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansion;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansionUtils;
import com.eviware.soapui.model.support.TestRunListenerAdapter;
import com.eviware.soapui.model.support.TestSuiteListenerAdapter;
import com.eviware.soapui.model.testsuite.TestCaseRunContext;
import com.eviware.soapui.model.testsuite.TestCaseRunner;
import com.eviware.soapui.model.testsuite.TestProperty;
import com.eviware.soapui.model.testsuite.TestPropertyListener;
import com.eviware.soapui.model.testsuite.TestStep;
import com.eviware.soapui.model.testsuite.TestStepResult;
import com.eviware.soapui.support.DocumentListenerAdapter;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.components.JComponentInspector;
import com.eviware.soapui.support.components.JInspectorPanel;
import com.eviware.soapui.support.components.JInspectorPanelFactory;
import com.eviware.soapui.support.components.JUndoableTextArea;
import com.eviware.soapui.support.components.JXToolBar;
import com.eviware.soapui.support.xml.XmlUtils;
import com.eviware.soapui.ui.desktop.DesktopPanel;
import com.eviware.soapui.ui.support.ModelItemDesktopPanel;

/**
 * DesktopPanel for TransferResponseValuesTestStep
 * 
 * @author Ole.Matzura
 */

public class PropertyTransfersDesktopPanel extends ModelItemDesktopPanel<PropertyTransfersTestStep>
{
	private final PropertyTransfersTestStep transferStep;
	private DefaultListModel listModel;
	private JList transferList;
	private JTextArea sourceArea;
	private JTextArea targetArea;
	private JButton copyButton;
	private JButton deleteButton;
	private JButton declareButton;
	private JComboBox sourcePropertyCombo;
	private JComboBox targetPropertyCombo;
	private JComboBox sourceStepCombo;
	private JComboBox targetStepCombo;
	private DefaultComboBoxModel sourceStepModel;
	private DefaultComboBoxModel targetStepModel;
	private TestStepPropertiesListener sourceStepPropertiesListener;
	private TestStepPropertiesListener targetStepPropertiesListener;
	private TransferPropertyChangeListener transferPropertyChangeListener = new TransferPropertyChangeListener();
	private boolean selecting;
	private InternalTestSuiteListener testSuiteListener;
	private TestRunComponentEnabler componentEnabler;
	private JCheckBox failTransferCheckBox;
	private JButton runButton;
	private JButton renameButton;
	private JCheckBox setNullCheckBox;
	private JCheckBox transferTextContentCheckBox;
	private JCheckBox ignoreEmptyCheckBox;
	private JCheckBox transferAllCheckBox;
	private JCheckBox entitizeCheckBox;
	private JCheckBox transferChildNodesCheckBox;
	private DesktopPanel resultDesktopPanel;
	private TransfersTableModel transferLogTableModel;
	private InternalTestRunListener testRunListener;
	private JComponentInspector<JComponent> logInspector;
	private JCheckBox useXQueryCheckBox;
	private JButton runAllButton;
	private JInspectorPanel inspectorPanel;
	private JXTable logTable;
	private JToggleButton disableButton;

	public PropertyTransfersDesktopPanel( PropertyTransfersTestStep testStep )
	{
		super( testStep );
		this.transferStep = testStep;
		componentEnabler = new TestRunComponentEnabler( testStep.getTestCase() );

		buildUI();

		testSuiteListener = new InternalTestSuiteListener();
		transferStep.getTestCase().getTestSuite().addTestSuiteListener( testSuiteListener );

		testRunListener = new InternalTestRunListener();
		transferStep.getTestCase().addTestRunListener( testRunListener );
	}

	protected void buildUI()
	{
		JSplitPane splitPane = UISupport.createHorizontalSplit();

		listModel = new DefaultListModel();

		for( int c = 0; c < transferStep.getTransferCount(); c++ )
		{
			String name = transferStep.getTransferAt( c ).getName();
			if( transferStep.getTransferAt( c ).isDisabled() )
				name += " (disabled)";

			listModel.addElement( name );
		}

		transferList = new JList( listModel );
		transferList.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
		transferList.addListSelectionListener( new TransferListSelectionListener() );
		componentEnabler.add( transferList );

		JScrollPane listScrollPane = new JScrollPane( transferList );
		UISupport.addTitledBorder( listScrollPane, "Transfers" );

		JPanel p = new JPanel( new BorderLayout() );
		p.add( listScrollPane, BorderLayout.CENTER );
		p.add( createPropertiesToolbar(), BorderLayout.NORTH );

		splitPane.setLeftComponent( p );

		JSplitPane innerSplit = UISupport.createVerticalSplit();
		innerSplit.setBorder( null );
		sourceArea = new JUndoableTextArea();
		sourceArea.setToolTipText( "XPath selection from source property" );
		sourceArea.setEnabled( false );
		sourceArea.getDocument().addDocumentListener( new SourceAreaDocumentListener() );
		componentEnabler.add( sourceArea );

		targetArea = new JUndoableTextArea();
		targetArea.setToolTipText( "XPath target in target property" );
		targetArea.setEnabled( false );
		targetArea.getDocument().addDocumentListener( new TargetAreaDocumentListener() );
		componentEnabler.add( targetArea );

		JPanel sourcePanel = new JPanel( new BorderLayout() );
		sourcePanel.add( new JScrollPane( sourceArea ), BorderLayout.CENTER );
		JXToolBar toolbar = createSourceToolbar();
		sourcePanel.add( toolbar, BorderLayout.NORTH );
		sourcePanel.setBorder( BorderFactory.createEmptyBorder( 0, 3, 3, 3 ) );
		innerSplit.setTopComponent( sourcePanel );

		JPanel targetPanel = new JPanel( new BorderLayout() );
		targetPanel.add( new JScrollPane( targetArea ), BorderLayout.CENTER );
		toolbar = createTargetToolbar();
		targetPanel.add( toolbar, BorderLayout.NORTH );
		targetPanel.setBorder( BorderFactory.createEmptyBorder( 0, 3, 3, 3 ) );

		innerSplit.setBottomComponent( targetPanel );

		innerSplit.setResizeWeight( 0.5 );
		innerSplit.setDividerLocation( 0.5 );

		JPanel panel = createTransferOptions();

		JPanel innerPanel = new JPanel( new BorderLayout() );
		innerPanel.add( innerSplit, BorderLayout.CENTER );
		innerPanel.add( panel, BorderLayout.SOUTH );
		innerPanel.add( createConfigToolbar(), BorderLayout.NORTH );

		splitPane.setRightComponent( innerPanel );
		splitPane.setResizeWeight( 0.1 );
		splitPane.setDividerLocation( 120 );

		inspectorPanel = JInspectorPanelFactory.build( splitPane );
		logInspector = new JComponentInspector<JComponent>( buildLog(), "Transfer Log (0)",
				"A log of performed transfers while the editor was open", true );
		inspectorPanel.addInspector( logInspector );
		add( inspectorPanel.getComponent(), BorderLayout.CENTER );

		setBorder( BorderFactory.createEmptyBorder( 3, 3, 3, 3 ) );
		setPreferredSize( new Dimension( 550, 400 ) );

		if( listModel.getSize() > 0 )
			transferList.setSelectedIndex( 0 );
		else
			setSelectedTransfer( null );

		componentEnabler.add( deleteButton );
		componentEnabler.add( declareButton );
		componentEnabler.add( runButton );
		componentEnabler.add( runAllButton );
		componentEnabler.add( copyButton );
		componentEnabler.add( renameButton );
		componentEnabler.add( failTransferCheckBox );
		componentEnabler.add( setNullCheckBox );
		componentEnabler.add( transferTextContentCheckBox );
		componentEnabler.add( ignoreEmptyCheckBox );
		componentEnabler.add( transferAllCheckBox );
		componentEnabler.add( useXQueryCheckBox );
		componentEnabler.add( entitizeCheckBox );
		componentEnabler.add( transferChildNodesCheckBox );

	}

	private JComponent buildLog()
	{
		JPanel logPanel = new JPanel( new BorderLayout() );

		transferLogTableModel = new TransfersTableModel();
		logTable = new JXTable( transferLogTableModel );
		logTable.getSelectionModel().addListSelectionListener( new ListSelectionListener()
		{

			public void valueChanged( ListSelectionEvent e )
			{
				int row = logTable.getSelectedRow();
				if( row != -1 )
				{
					String transferName = transferLogTableModel.getValueAt( row, 1 ).toString();
					int ix = listModel.indexOf( transferName );
					if( ix != -1 )
					{
						transferList.setSelectedIndex( ix );
					}
				}
			}
		} );

		logTable.setHorizontalScrollEnabled( true );
		logTable.packAll();

		JXToolBar toolbar = UISupport.createSmallToolbar();
		toolbar.add( new ClearLogAction() );

		JScrollPane scrollPane = new JScrollPane( logTable );
		scrollPane.setBorder( BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder( 3, 3, 3, 3 ),
				scrollPane.getBorder() ) );

		logPanel.add( toolbar, BorderLayout.NORTH );
		logPanel.add( scrollPane, BorderLayout.CENTER );

		return logPanel;
	}

	protected JXToolBar createPropertiesToolbar()
	{
		JXToolBar toolbar = UISupport.createSmallToolbar();
		toolbar.addFixed( UISupport.createToolbarButton( new AddAction() ) );
		deleteButton = UISupport.createToolbarButton( new DeleteAction() );
		deleteButton.setEnabled( false );
		toolbar.addFixed( deleteButton );
		copyButton = UISupport.createToolbarButton( new CopyAction() );
		copyButton.setEnabled( false );
		toolbar.addFixed( copyButton );
		renameButton = UISupport.createToolbarButton( new RenameAction() );
		renameButton.setEnabled( false );
		toolbar.addFixed( renameButton );

		disableButton = new JToggleButton( new DisableAction() );
		disableButton.setPreferredSize( UISupport.TOOLBAR_BUTTON_DIMENSION );
		disableButton.setSelectedIcon( UISupport.createImageIcon( "/bullet_red.png" ) );
		toolbar.addSeparator();
		toolbar.addFixed( disableButton );

		return toolbar;
	}

	protected JXToolBar createConfigToolbar()
	{
		JXToolBar toolbar = UISupport.createToolbar();

		toolbar.setBorder( BorderFactory.createEmptyBorder( 2, 2, 2, 2 ) );

		runButton = UISupport.createToolbarButton( new RunAction() );
		runButton.setEnabled( transferList.getSelectedIndex() != -1 );
		toolbar.addFixed( runButton );

		runAllButton = UISupport.createToolbarButton( new RunAllAction() );
		runAllButton.setEnabled( transferStep.getTransferCount() > 0 );
		toolbar.addFixed( runAllButton );

		declareButton = UISupport.createToolbarButton( new DeclareNamespacesAction() );
		declareButton.setEnabled( false );
		toolbar.addFixed( declareButton );
		toolbar.addGlue();
		toolbar.addFixed( UISupport
				.createToolbarButton( new ShowOnlineHelpAction( HelpUrls.TRANSFERSTEPEDITOR_HELP_URL ) ) );
		return toolbar;
	}

	protected JPanel createTransferOptions()
	{
		JPanel panel = new JPanel( new GridLayout( 4, 2 ) );
		failTransferCheckBox = new JCheckBox( "Fail transfer on error", false );
		failTransferCheckBox.setToolTipText( "Fails the Property Transfer Step if an error occurs" );
		failTransferCheckBox.addChangeListener( new ChangeListener()
		{

			public void stateChanged( ChangeEvent e )
			{
				PropertyTransfer currentTransfer = getCurrentTransfer();
				if( currentTransfer != null )
				{
					currentTransfer.setFailOnError( failTransferCheckBox.isSelected() );
				}
			}
		} );

		setNullCheckBox = new JCheckBox( "Set null on missing source", false );
		setNullCheckBox.setToolTipText( "Will set target to null if source is missing or null" );
		setNullCheckBox.addChangeListener( new ChangeListener()
		{

			public void stateChanged( ChangeEvent e )
			{
				PropertyTransfer currentTransfer = getCurrentTransfer();
				if( currentTransfer != null )
				{
					currentTransfer.setSetNullOnMissingSource( setNullCheckBox.isSelected() );
				}
			}
		} );

		transferTextContentCheckBox = new JCheckBox( "Transfer text content", false );
		transferTextContentCheckBox.setToolTipText( "Will only transfer text content of source/target elements" );
		transferTextContentCheckBox.addChangeListener( new ChangeListener()
		{

			public void stateChanged( ChangeEvent e )
			{
				PropertyTransfer currentTransfer = getCurrentTransfer();
				if( currentTransfer != null )
				{
					currentTransfer.setTransferTextContent( transferTextContentCheckBox.isSelected() );
				}
			}
		} );

		ignoreEmptyCheckBox = new JCheckBox( "Ignore empty/missing values", false );
		ignoreEmptyCheckBox.setToolTipText( "Will not transfer empty or missing values" );
		ignoreEmptyCheckBox.addChangeListener( new ChangeListener()
		{

			public void stateChanged( ChangeEvent e )
			{
				PropertyTransfer currentTransfer = getCurrentTransfer();
				if( currentTransfer != null )
				{
					currentTransfer.setIgnoreEmpty( ignoreEmptyCheckBox.isSelected() );
				}
			}
		} );

		transferAllCheckBox = new JCheckBox( "Transfer to all", false );
		transferAllCheckBox.setToolTipText( "Will transfer to all matching target selections" );
		transferAllCheckBox.addChangeListener( new ChangeListener()
		{

			public void stateChanged( ChangeEvent e )
			{
				PropertyTransfer currentTransfer = getCurrentTransfer();
				if( currentTransfer != null )
				{
					currentTransfer.setTransferToAll( transferAllCheckBox.isSelected() );
				}
			}
		} );

		useXQueryCheckBox = new JCheckBox( "Use XQuery", false );
		useXQueryCheckBox.setToolTipText( "Interprets the source xpath as an XQuery expression" );
		useXQueryCheckBox.addChangeListener( new ChangeListener()
		{

			public void stateChanged( ChangeEvent e )
			{
				PropertyTransfer currentTransfer = getCurrentTransfer();
				if( currentTransfer != null )
				{
					currentTransfer.setUseXQuery( useXQueryCheckBox.isSelected() );
				}
			}
		} );

		entitizeCheckBox = new JCheckBox( "Entitize transferred value(s)", false );
		entitizeCheckBox.setToolTipText( "Entitize transferred values when possible" );
		entitizeCheckBox.addChangeListener( new ChangeListener()
		{

			public void stateChanged( ChangeEvent e )
			{
				PropertyTransfer currentTransfer = getCurrentTransfer();
				if( currentTransfer != null )
				{
					currentTransfer.setEntitize( entitizeCheckBox.isSelected() );
				}
			}
		} );

		transferChildNodesCheckBox = new JCheckBox( "Transfer Child Nodes", false );
		transferChildNodesCheckBox
				.setToolTipText( "Transfers child nodes of specified source node to children of specified target" );
		transferChildNodesCheckBox.addChangeListener( new ChangeListener()
		{

			public void stateChanged( ChangeEvent e )
			{
				PropertyTransfer currentTransfer = getCurrentTransfer();
				if( currentTransfer != null )
				{
					currentTransfer.setTransferChildNodes( transferChildNodesCheckBox.isSelected() );
				}
			}
		} );

		panel.add( failTransferCheckBox );
		panel.add( setNullCheckBox );
		panel.add( transferTextContentCheckBox );
		panel.add( ignoreEmptyCheckBox );
		panel.add( transferAllCheckBox );
		panel.add( useXQueryCheckBox );
		panel.add( entitizeCheckBox );
		panel.add( transferChildNodesCheckBox );
		panel.setBorder( BorderFactory.createEmptyBorder( 3, 3, 3, 3 ) );
		return panel;
	}

	protected JXToolBar createTargetToolbar()
	{
		JXToolBar toolbar;
		toolbar = UISupport.createToolbar();
		toolbar.addSpace( 3 );
		toolbar.addFixed( new JLabel( "<html><b>Target: </b></html>" ) );
		toolbar.addUnrelatedGap();

		targetStepCombo.setSelectedItem( null );
		targetStepCombo.setToolTipText( "The step the value will be transferred to" );
		targetStepCombo.setEnabled( false );
		targetStepCombo.addItemListener( new StepComboItemListener( targetPropertyCombo, targetStepPropertiesListener ) );
		targetStepCombo.addItemListener( new ItemListener()
		{

			public void itemStateChanged( ItemEvent e )
			{
				if( e.getStateChange() == ItemEvent.SELECTED && !selecting )
				{
					TestPropertyHolder targetStep = ( TestPropertyHolder )targetStepCombo.getSelectedItem();
					PropertyTransfer valueTransfer = getCurrentTransfer();

					if( valueTransfer != null )
					{
						String name = "";

						if( targetStep == PropertyExpansionUtils.getGlobalProperties() )
							name = PropertyExpansion.GLOBAL_REFERENCE;
						else if( targetStep == transferStep.getTestCase().getTestSuite().getProject() )
							name = PropertyExpansion.PROJECT_REFERENCE;
						else if( targetStep == transferStep.getTestCase().getTestSuite() )
							name = PropertyExpansion.TESTSUITE_REFERENCE;
						else if( targetStep == transferStep.getTestCase() )
							name = PropertyExpansion.TESTCASE_REFERENCE;
						else
							name = targetStep.getModelItem().getName();

						valueTransfer.setTargetStepName( name );
					}
				}
			}
		} );

		toolbar.add( UISupport.setFixedSize( targetStepCombo, 180, 21 ) );
		toolbar.addUnrelatedGap();

		toolbar.addFixed( new JLabel( " Property: " ) );
		toolbar.addRelatedGap();

		targetPropertyCombo.setToolTipText( "The property the value will be transferred to" );
		targetPropertyCombo.setEnabled( false );
		targetPropertyCombo.addItemListener( new ItemListener()
		{

			public void itemStateChanged( ItemEvent e )
			{
				if( e.getStateChange() == ItemEvent.SELECTED && !selecting )
				{
					TestProperty targetProperty = ( TestProperty )targetPropertyCombo.getSelectedItem();
					PropertyTransfer valueTransfer = getCurrentTransfer();

					if( valueTransfer != null )
					{
						valueTransfer.setTargetPropertyName( targetProperty.getName() );
					}
				}
			}
		} );

		toolbar.add( UISupport.setFixedSize( targetPropertyCombo, 130, 21 ) );
		toolbar.addGlue();
		return toolbar;
	}

	protected JXToolBar createSourceToolbar()
	{
		JXToolBar toolbar = UISupport.createToolbar();
		toolbar.addSpace( 3 );
		toolbar.addFixed( new JLabel( "<html><b>Source: </b></html>" ) );
		toolbar.addUnrelatedGap();

		sourcePropertyCombo = UISupport.addTooltipListener( new JComboBox(), "Source Property" );
		sourceStepModel = new DefaultComboBoxModel();
		sourceStepCombo = UISupport.addTooltipListener( new JComboBox( sourceStepModel ),
				"Source Step or Property Container" );
		sourceStepCombo.setRenderer( new StepComboRenderer() );
		sourcePropertyCombo.setRenderer( new PropertyComboRenderer() );

		componentEnabler.add( sourcePropertyCombo );
		componentEnabler.add( sourceStepCombo );

		targetPropertyCombo = UISupport.addTooltipListener( new JComboBox(), "Target Property" );
		targetStepModel = new DefaultComboBoxModel();
		targetStepCombo = UISupport.addTooltipListener( new JComboBox( targetStepModel ),
				"Target Step or Property Container" );
		targetStepCombo.setRenderer( new StepComboRenderer() );
		targetPropertyCombo.setRenderer( new PropertyComboRenderer() );

		componentEnabler.add( targetPropertyCombo );
		componentEnabler.add( targetStepCombo );

		sourceStepPropertiesListener = new TestStepPropertiesListener( sourcePropertyCombo );
		targetStepPropertiesListener = new TestStepPropertiesListener( targetPropertyCombo );

		sourceStepModel.addElement( PropertyExpansionUtils.getGlobalProperties() );
		sourceStepModel.addElement( transferStep.getTestCase().getTestSuite().getProject() );
		sourceStepModel.addElement( transferStep.getTestCase().getTestSuite() );
		sourceStepModel.addElement( transferStep.getTestCase() );

		for( int c = 0; c < transferStep.getTestCase().getTestStepCount(); c++ )
		{
			WsdlTestStep testStep = transferStep.getTestCase().getTestStepAt( c );
			if( testStep == transferStep )
				continue;

			sourceStepModel.addElement( testStep );
		}

		for( int c = 0; c < sourceStepModel.getSize(); c++ )
			targetStepModel.addElement( sourceStepModel.getElementAt( c ) );

		sourceStepCombo.setSelectedItem( null );
		sourceStepCombo.setEnabled( false );
		sourceStepCombo.addItemListener( new StepComboItemListener( sourcePropertyCombo, sourceStepPropertiesListener ) );
		sourceStepCombo.addItemListener( new ItemListener()
		{

			public void itemStateChanged( ItemEvent e )
			{
				if( e.getStateChange() == ItemEvent.SELECTED && !selecting )
				{
					TestPropertyHolder sourceStep = ( TestPropertyHolder )sourceStepCombo.getSelectedItem();
					PropertyTransfer valueTransfer = getCurrentTransfer();

					if( valueTransfer != null )
					{
						String name = "";

						if( sourceStep == PropertyExpansionUtils.getGlobalProperties() )
							name = PropertyExpansion.GLOBAL_REFERENCE;
						else if( sourceStep == transferStep.getTestCase().getTestSuite().getProject() )
							name = PropertyExpansion.PROJECT_REFERENCE;
						else if( sourceStep == transferStep.getTestCase().getTestSuite() )
							name = PropertyExpansion.TESTSUITE_REFERENCE;
						else if( sourceStep == transferStep.getTestCase() )
							name = PropertyExpansion.TESTCASE_REFERENCE;
						else
							name = sourceStep.getModelItem().getName();

						valueTransfer.setSourceStepName( name );
					}
				}
			}
		} );

		toolbar.add( UISupport.setFixedSize( sourceStepCombo, 180, 21 ) );
		toolbar.addUnrelatedGap();

		toolbar.addFixed( new JLabel( " Property: " ) );
		toolbar.addRelatedGap();

		sourcePropertyCombo.setToolTipText( "The property the value will be transferred from" );
		sourcePropertyCombo.setEnabled( false );
		sourcePropertyCombo.addItemListener( new ItemListener()
		{

			public void itemStateChanged( ItemEvent e )
			{
				if( e.getStateChange() == ItemEvent.SELECTED && !selecting )
				{
					TestProperty sourceProperty = ( TestProperty )sourcePropertyCombo.getSelectedItem();
					PropertyTransfer valueTransfer = getCurrentTransfer();

					if( valueTransfer != null )
					{
						valueTransfer.setSourcePropertyName( sourceProperty.getName() );
					}
				}
			}
		} );

		toolbar.add( UISupport.setFixedSize( sourcePropertyCombo, 130, 21 ) );
		toolbar.addGlue();
		return toolbar;
	}

	public PropertyTransfer getCurrentTransfer()
	{
		int ix = transferList.getSelectedIndex();
		return ix == -1 ? null : transferStep.getTransferAt( ix );
	}

	/**
	 * Listen for testStep property changes and update properties combo
	 * accordingly
	 */

	private final class TestStepPropertiesListener implements TestPropertyListener
	{
		private final JComboBox combo;

		public TestStepPropertiesListener( JComboBox combo )
		{
			this.combo = combo;
		}

		public void propertyAdded( String name )
		{
			TestProperty property = combo == targetPropertyCombo ? getCurrentTransfer().getTargetStep().getProperty( name )
					: getCurrentTransfer().getSourceStep().getProperty( name );

			combo.addItem( property );
			combo.setEnabled( true );
		}

		public void propertyRemoved( String name )
		{
			if( combo.getSelectedItem() != null && ( ( TestProperty )combo.getSelectedItem() ).getName().equals( name ) )
				combo.setSelectedItem( null );

			for( int c = 0; c < combo.getItemCount(); c++ )
			{
				if( ( ( TestProperty )combo.getItemAt( c ) ).getName().equals( name ) )
				{
					combo.removeItemAt( c );
					break;
				}
			}

			combo.setEnabled( combo.getItemCount() > 0 );
		}

		public void propertyRenamed( String oldName, String newName )
		{
		}

		public void propertyValueChanged( String name, String oldValue, String newValue )
		{
		}

		public void propertyMoved( String name, int oldIndex, int newIndex )
		{
			combo.removeItemAt( oldIndex );

			TestProperty property = combo == targetPropertyCombo ? getCurrentTransfer().getTargetStep().getProperty( name )
					: getCurrentTransfer().getSourceStep().getProperty( name );

			combo.insertItemAt( property, newIndex );
		}
	}

	/**
	 * Listen for teststep changes and update source/target step combos
	 * accordingly
	 */

	private final class InternalTestSuiteListener extends TestSuiteListenerAdapter
	{
		public void testStepAdded( TestStep testStep, int index )
		{
			if( testStep.getTestCase() == transferStep.getTestCase() )
			{
				sourceStepModel.addElement( testStep );
				targetStepModel.addElement( testStep );
			}
		}

		public void testStepMoved( TestStep testStep, int fromIndex, int offset )
		{
			if( testStep.getTestCase() == transferStep.getTestCase() )
			{
				String testStepName = testStep.getName();
				if( sourceStepModel.getIndexOf( testStepName ) == fromIndex )
				{
					String sourceStep = ( String )sourceStepCombo.getSelectedItem();
					String sourceProperty = ( String )sourcePropertyCombo.getSelectedItem();

					sourceStepModel.removeElementAt( fromIndex );
					if( fromIndex + offset > sourceStepModel.getSize() )
						sourceStepModel.addElement( testStepName );
					else
						sourceStepModel.insertElementAt( testStepName, fromIndex + offset );

					sourceStepCombo.setSelectedItem( sourceStep );
					sourcePropertyCombo.setSelectedItem( sourceProperty );
				}

				if( targetStepModel.getIndexOf( testStepName ) == fromIndex )
				{
					String targetStep = ( String )targetStepCombo.getSelectedItem();
					String targetProperty = ( String )targetPropertyCombo.getSelectedItem();

					targetStepModel.removeElementAt( fromIndex );
					if( fromIndex + offset > targetStepModel.getSize() )
						targetStepModel.addElement( testStepName );
					else
						targetStepModel.insertElementAt( testStepName, fromIndex + offset );

					targetStepCombo.setSelectedItem( targetStep );
					targetPropertyCombo.setSelectedItem( targetProperty );
				}
			}
		}

		public void testStepRemoved( TestStep testStep, int index )
		{
			if( testStep.getTestCase() == transferStep.getTestCase() )
			{
				sourceStepModel.removeElement( testStep );
				targetStepModel.removeElement( testStep );
			}
		}
	}

	/**
	 * Listen to step selections and update properties combo accordingly
	 */

	private final class StepComboItemListener implements ItemListener
	{
		private final JComboBox propertyCombo;
		private final TestStepPropertiesListener testStepPropertiesListener;

		public StepComboItemListener( final JComboBox propertyCombo, TestStepPropertiesListener testStepPropertiesListener )
		{
			this.propertyCombo = propertyCombo;
			this.testStepPropertiesListener = testStepPropertiesListener;
		}

		public void itemStateChanged( ItemEvent e )
		{
			if( e.getStateChange() == ItemEvent.SELECTED )
			{
				TestPropertyHolder selectedItem = ( TestPropertyHolder )e.getItem();
				String[] propertyNames = selectedItem.getPropertyNames();

				// remove read-only properties from target property
				if( propertyCombo == targetPropertyCombo )
				{
					List<String> names = new ArrayList<String>();
					for( String name : propertyNames )
					{
						TestProperty property = selectedItem.getProperty( name );
						if( property != null && !property.isReadOnly() )
							names.add( property.getName() );
					}

					propertyNames = names.toArray( new String[names.size()] );
				}

				DefaultComboBoxModel model = new DefaultComboBoxModel();
				for( String name : propertyNames )
					model.addElement( selectedItem.getProperty( name ) );

				propertyCombo.setModel( model );
				propertyCombo.setEnabled( propertyNames.length > 0 );

				if( propertyCombo == targetPropertyCombo )
					propertyCombo.setSelectedItem( getCurrentTransfer().getTargetProperty() );
				else
					propertyCombo.setSelectedItem( getCurrentTransfer().getSourceProperty() );

				selectedItem.addTestPropertyListener( testStepPropertiesListener );
			}
			else
			{
				propertyCombo.removeAllItems();
				propertyCombo.setEnabled( false );
			}
		}
	}

	/**
	 * Handle updates to source path
	 */

	private final class SourceAreaDocumentListener extends DocumentListenerAdapter
	{
		public void update( Document document )
		{
			int ix = transferList.getSelectedIndex();
			if( ix != -1 )
			{
				transferStep.getTransferAt( ix ).setSourcePath( sourceArea.getText() );
			}
		}
	}

	/**
	 * Handle updates to target path
	 */

	private final class TargetAreaDocumentListener extends DocumentListenerAdapter
	{
		public void update( Document document )
		{
			int ix = transferList.getSelectedIndex();
			if( ix != -1 )
			{
				transferStep.getTransferAt( ix ).setTargetPath( targetArea.getText() );
			}
		}
	}

	/**
	 * Listen to selection changes in transfer list and update controls
	 * accordingly
	 */

	private final class TransferListSelectionListener implements ListSelectionListener
	{
		private PropertyTransfer transfer;

		public void valueChanged( ListSelectionEvent e )
		{
			selecting = true;

			if( transfer != null )
			{
				transfer.removePropertyChangeListener( transferPropertyChangeListener );
			}

			transfer = getCurrentTransfer();
			setSelectedTransfer( transfer );
			selecting = false;
		}
	}

	private void setSelectedTransfer( PropertyTransfer transfer )
	{
		if( transfer == null )
		{
			sourceArea.setText( "" );
			targetArea.setText( "" );

			sourcePropertyCombo.removeAllItems();
			targetPropertyCombo.removeAllItems();

			sourceStepCombo.setSelectedIndex( -1 );
			targetStepCombo.setSelectedIndex( -1 );
		}
		else
		{
			transfer.addPropertyChangeListener( transferPropertyChangeListener );

			sourceArea.setText( transfer.getSourcePath() );
			sourceArea.setCaretPosition( 0 );
			targetArea.setText( transfer.getTargetPath() );
			targetArea.setCaretPosition( 0 );

			sourceStepCombo.setSelectedItem( transfer.getSourceStep() );
			sourcePropertyCombo.setSelectedItem( transfer.getSourceProperty() );

			targetStepCombo.setSelectedItem( transfer.getTargetStep() );
			targetPropertyCombo.setSelectedItem( transfer.getTargetProperty() );

			failTransferCheckBox.setSelected( transfer.getFailOnError() );
			setNullCheckBox.setSelected( transfer.getSetNullOnMissingSource() );
			transferTextContentCheckBox.setSelected( transfer.getTransferTextContent() );
			ignoreEmptyCheckBox.setSelected( transfer.getIgnoreEmpty() );
			transferAllCheckBox.setSelected( transfer.getTransferToAll() );
			useXQueryCheckBox.setSelected( transfer.getUseXQuery() );
			entitizeCheckBox.setSelected( transfer.getEntitize() );
			transferChildNodesCheckBox.setSelected( transfer.getTransferChildNodes() );

			disableButton.setSelected( transfer.isDisabled() );
		}

		copyButton.setEnabled( transfer != null );
		renameButton.setEnabled( transfer != null );
		deleteButton.setEnabled( transfer != null );
		disableButton.setEnabled( transfer != null );
		declareButton.setEnabled( transfer != null );
		sourceStepCombo.setEnabled( transfer != null );
		targetStepCombo.setEnabled( transfer != null );
		sourceArea.setEnabled( transfer != null );
		targetArea.setEnabled( transfer != null );
		failTransferCheckBox.setEnabled( transfer != null );
		setNullCheckBox.setEnabled( transfer != null );
		transferTextContentCheckBox.setEnabled( transfer != null );
		ignoreEmptyCheckBox.setEnabled( transfer != null );
		transferAllCheckBox.setEnabled( transfer != null );
		useXQueryCheckBox.setEnabled( transfer != null );
		entitizeCheckBox.setEnabled( transfer != null );
		transferChildNodesCheckBox.setEnabled( transfer != null );

		runAllButton.setEnabled( transferList.getModel().getSize() > 0 );
		runButton.setEnabled( transfer != null );

		sourcePropertyCombo.setEnabled( transfer != null );
		targetPropertyCombo.setEnabled( transfer != null );
	}

	/**
	 * Listen to property changes and update UI objects. These may have been
	 * triggered by UI so first check for actual difference so we dont end up in
	 * loop.
	 */

	private class TransferPropertyChangeListener implements PropertyChangeListener
	{
		public void propertyChange( PropertyChangeEvent evt )
		{
			Object newValue = evt.getNewValue();

			if( evt.getPropertyName().equals( PropertyTransfer.SOURCE_PATH_PROPERTY ) )
			{
				if( !sourceArea.getText().equals( newValue ) )
					sourceArea.setText( ( String )newValue );
			}
			else if( evt.getPropertyName().equals( PropertyTransfer.TARGET_PATH_PROPERTY ) )
			{
				if( !targetArea.getText().equals( newValue ) )
					targetArea.setText( ( String )newValue );
			}
			else if( evt.getPropertyName().equals( PropertyTransfer.SOURCE_STEP_PROPERTY ) )
			{
				Object selectedItem = sourceStepCombo.getSelectedItem();
				if( newValue == null || selectedItem == null || !selectedItem.equals( newValue ) )
				{
					selecting = true;
					sourceStepCombo.setSelectedItem( newValue );
					selecting = false;
				}
			}
			else if( evt.getPropertyName().equals( PropertyTransfer.TARGET_STEP_PROPERTY ) )
			{
				Object selectedItem = targetStepCombo.getSelectedItem();
				if( newValue == null || selectedItem == null || !selectedItem.equals( newValue ) )
				{
					selecting = true;
					targetStepCombo.setSelectedItem( newValue );
					selecting = false;
				}
			}
			else if( evt.getPropertyName().equals( PropertyTransfer.SOURCE_TYPE_PROPERTY ) )
			{
				Object selectedItem = sourcePropertyCombo.getSelectedItem();
				if( selectedItem == null || !selectedItem.equals( newValue ) )
					sourcePropertyCombo.setSelectedItem( newValue );
			}
			else if( evt.getPropertyName().equals( PropertyTransfer.TARGET_TYPE_PROPERTY ) )
			{
				Object selectedItem = targetPropertyCombo.getSelectedItem();
				if( selectedItem == null || !selectedItem.equals( newValue ) )
					targetPropertyCombo.setSelectedItem( newValue );
			}
		}
	}

	private final class AddAction extends AbstractAction
	{
		public AddAction()
		{
			putValue( Action.SHORT_DESCRIPTION, "Adds a new Property Transfer" );
			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/add_property.gif" ) );
		}

		public void actionPerformed( ActionEvent e )
		{
			String name = UISupport.prompt( "Specify name for value transfer", "Add Transfer", "" );
			if( name == null || name.trim().length() == 0 )
				return;

			transferStep.addTransfer( name );

			listModel.addElement( name );
			transferList.setSelectedIndex( listModel.getSize() - 1 );
		}
	}

	private final class CopyAction extends AbstractAction
	{
		public CopyAction()
		{
			putValue( Action.SHORT_DESCRIPTION, "Copies the selected Property Transfer" );
			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/clone_request.gif" ) );
		}

		public void actionPerformed( ActionEvent e )
		{
			int ix = transferList.getSelectedIndex();
			PropertyTransfer config = transferStep.getTransferAt( ix );

			String name = UISupport.prompt( "Specify name for value transfer", "Copy Transfer", config.getName() );
			if( name == null || name.trim().length() == 0 )
				return;

			PropertyTransfer transfer = transferStep.addTransfer( name );
			transfer.setSourceStepName( config.getSourceStepName() );
			transfer.setSourcePropertyName( config.getSourcePropertyName() );
			transfer.setSourcePath( config.getSourcePath() );
			transfer.setTargetStepName( config.getTargetStepName() );
			transfer.setTargetPropertyName( config.getTargetPropertyName() );
			transfer.setTargetPath( config.getTargetPath() );
			transfer.setDisabled( config.isDisabled() );
			transfer.setEntitize( config.getEntitize() );
			transfer.setFailOnError( config.getFailOnError() );
			transfer.setIgnoreEmpty( config.getIgnoreEmpty() );
			transfer.setSetNullOnMissingSource( config.getSetNullOnMissingSource() );
			transfer.setTransferChildNodes( config.getTransferChildNodes() );
			transfer.setTransferTextContent( config.getTransferTextContent() );
			transfer.setTransferToAll( config.getTransferToAll() );
			transfer.setUseXQuery( config.getUseXQuery() );

			listModel.addElement( name );
			transferList.setSelectedIndex( listModel.getSize() - 1 );
		}
	}

	private final class DeleteAction extends AbstractAction
	{
		public DeleteAction()
		{
			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/remove_property.gif" ) );
			putValue( Action.SHORT_DESCRIPTION, "Deletes the selected Property Transfer" );
		}

		public void actionPerformed( ActionEvent e )
		{
			if( UISupport.confirm( "Delete selected transfer", "Delete Transfer" ) )
			{
				transferList.setSelectedIndex( -1 );

				int ix = transferList.getSelectedIndex();
				transferStep.removeTransferAt( ix );
				listModel.remove( ix );

				if( listModel.getSize() > 0 )
				{
					transferList.setSelectedIndex( ix > listModel.getSize() - 1 ? listModel.getSize() - 1 : ix );
				}
			}
		}
	}

	private final class ClearLogAction extends AbstractAction
	{
		public ClearLogAction()
		{
			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/clear_properties.gif" ) );
			putValue( Action.SHORT_DESCRIPTION, "Clears the property-transfer log" );
		}

		public void actionPerformed( ActionEvent e )
		{
			transferLogTableModel.clear();
		}
	}

	private final class RenameAction extends AbstractAction
	{
		public RenameAction()
		{
			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/rename.gif" ) );
			putValue( Action.SHORT_DESCRIPTION, "Renames the selected Property Transfer" );
		}

		public void actionPerformed( ActionEvent e )
		{
			PropertyTransfer transfer = getCurrentTransfer();

			String newName = UISupport.prompt( "Specify new name for transfer", "Rename Transfer", transfer.getName() );

			if( newName != null && !transfer.getName().equals( newName ) )
			{
				listModel.setElementAt( newName, transferList.getSelectedIndex() );
				transfer.setName( newName );
			}
		}
	}

	private final class DisableAction extends AbstractAction
	{
		public DisableAction()
		{
			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/bullet_green.png" ) );
			putValue( Action.SHORT_DESCRIPTION, "Disables the selected Property Transfer" );
		}

		public void actionPerformed( ActionEvent e )
		{
			PropertyTransfer transfer = getCurrentTransfer();
			transfer.setDisabled( disableButton.isSelected() );

			String name = transfer.getName();
			if( transfer.isDisabled() )
				name += " (disabled)";

			listModel.setElementAt( name, transferList.getSelectedIndex() );
		}
	}

	private final class DeclareNamespacesAction extends AbstractAction
	{
		public DeclareNamespacesAction()
		{
			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/declareNs.gif" ) );
			putValue( Action.SHORT_DESCRIPTION,
					"Declare available response/request namespaces in source/target expressions" );
		}

		public void actionPerformed( ActionEvent e )
		{
			try
			{
				TestPropertyHolder previousStep = getCurrentTransfer().getSourceStep();

				if( previousStep instanceof WsdlTestRequestStep )
				{
					WsdlTestRequest testRequest = ( ( WsdlTestRequestStep )previousStep ).getTestRequest();
					sourceArea.setText( XmlUtils.declareXPathNamespaces( testRequest.getOperation().getInterface() )
							+ sourceArea.getText() );
				}
				else
					UISupport.showErrorMessage( "Property Source is not a Request" );

				TestPropertyHolder nextStep = getCurrentTransfer().getTargetStep();

				if( nextStep instanceof WsdlTestRequestStep )
				{
					WsdlTestRequest testRequest = ( ( WsdlTestRequestStep )nextStep ).getTestRequest();
					targetArea.setText( XmlUtils.declareXPathNamespaces( testRequest.getOperation().getInterface() )
							+ targetArea.getText() );
				}
				else
					UISupport.showErrorMessage( "Property Target is not a Request" );
			}
			catch( Exception e1 )
			{
				UISupport.showErrorMessage( e1 );
			}
		}
	}

	private final class RunAllAction extends AbstractAction
	{
		public RunAllAction()
		{
			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/run_all.gif" ) );
			putValue( Action.SHORT_DESCRIPTION, "Runs all Property Transfers" );
		}

		public void actionPerformed( ActionEvent e )
		{
			if( listModel.getSize() == 0 )
			{
				UISupport.showErrorMessage( "Missing transfers!" );
				return;
			}

			MockTestRunner mockRunner = new MockTestRunner( transferStep.getTestCase() );
			MockTestRunContext context = new MockTestRunContext( mockRunner, transferStep );

			for( int c = 0; c < transferStep.getTransferCount(); c++ )
			{
				PropertyTransfer transfer = transferStep.getTransferAt( c );
				PropertyTransfersTestStep.PropertyTransferResult result = ( PropertyTransfersTestStep.PropertyTransferResult )transferStep
						.run( mockRunner, context, transfer );
				transferLogTableModel.addResult( result );
			}
		}
	}

	private final class RunAction extends AbstractAction
	{
		public RunAction()
		{
			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/run.gif" ) );
			putValue( Action.SHORT_DESCRIPTION, "Runs selected PropertyTransfer" );
		}

		public void actionPerformed( ActionEvent e )
		{
			if( transferList.getSelectedIndex() == -1 )
			{
				UISupport.showErrorMessage( "No transfer selectd!" );
				return;
			}

			MockTestRunner mockRunner = new MockTestRunner( transferStep.getTestCase() );
			MockTestRunContext context = new MockTestRunContext( mockRunner, transferStep );
			PropertyTransferResult result = ( PropertyTransferResult )transferStep.run( mockRunner, context,
					getCurrentTransfer() );
			transferLogTableModel.addResult( result );
		}
	}

	public boolean onClose( boolean canCancel )
	{
		transferStep.getTestCase().getTestSuite().removeTestSuiteListener( testSuiteListener );
		transferStep.getTestCase().removeTestRunListener( testRunListener );

		PropertyTransfer transfer = getCurrentTransfer();

		if( transfer != null )
		{
			transfer.removePropertyChangeListener( transferPropertyChangeListener );
		}

		TestPropertyHolder item = ( TestPropertyHolder )sourceStepCombo.getSelectedItem();
		if( item != null )
		{
			item.removeTestPropertyListener( sourceStepPropertiesListener );
		}

		item = ( TestPropertyHolder )targetStepCombo.getSelectedItem();
		if( item != null )
		{
			item.removeTestPropertyListener( targetStepPropertiesListener );
		}

		componentEnabler.release();
		if( resultDesktopPanel != null )
			SoapUI.getDesktop().closeDesktopPanel( resultDesktopPanel );

		inspectorPanel.release();

		return release();
	}

	public JComponent getComponent()
	{
		return this;
	}

	protected JTextArea getSourceArea()
	{
		return sourceArea;
	}

	protected JTextArea getTargetArea()
	{
		return targetArea;
	}

	public boolean dependsOn( ModelItem modelItem )
	{
		return modelItem == transferStep || modelItem == transferStep.getTestCase()
				|| modelItem == transferStep.getTestCase().getTestSuite()
				|| modelItem == transferStep.getTestCase().getTestSuite().getProject();
	}

	public boolean selectTransfer( PropertyTransfer transfer )
	{
		for( int c = 0; c < transferStep.getTransferCount(); c++ )
		{
			if( transferStep.getTransferAt( c ) == transfer )
			{
				transferList.setSelectedIndex( c );
				return true;
			}
		}

		return false;
	}

	private class TransfersTableModel extends AbstractTableModel
	{
		private List<PropertyTransfersTestStep.PropertyTransferResult> results = new ArrayList<PropertyTransfersTestStep.PropertyTransferResult>();

		public synchronized int getRowCount()
		{
			int sum = 0;
			for( PropertyTransfersTestStep.PropertyTransferResult result : results )
			{
				sum += result.getTransferCount();
			}

			return sum;
		}

		public synchronized void clear()
		{
			results.clear();
			fireTableDataChanged();
			logInspector.setTitle( "Transfer Log (0)" );
		}

		public void addResult( PropertyTransfersTestStep.PropertyTransferResult result )
		{
			int rowCount;
			synchronized( this )
			{
				rowCount = getRowCount();
				results.add( result );
			}

			fireTableRowsInserted( rowCount, rowCount + result.getTransferCount() );

			logInspector.setTitle( "Transfer Log (" + getRowCount() + ")" );
			inspectorPanel.activate( logInspector );
		}

		public int getColumnCount()
		{
			return 3;
		}

		public String getColumnName( int column )
		{
			switch( column )
			{
			case 0 :
				return "Timestamp";
			case 1 :
				return "Transfer Name";
			case 2 :
				return "Transferred Values";
			}

			return null;
		}

		public synchronized Object getValueAt( int rowIndex, int columnIndex )
		{
			// find correct transfer
			PropertyTransfersTestStep.PropertyTransferResult result = null;
			int sum = 0;

			for( int c = 0; c < results.size(); c++ )
			{
				if( sum + results.get( c ).getTransferCount() > rowIndex )
				{
					result = results.get( c );
					break;
				}
				else
				{
					sum += results.get( c ).getTransferCount();
				}
			}

			if( result != null )
			{
				switch( columnIndex )
				{
				case 0 :
					return new Date( result.getTimeStamp() ).toString();
				case 1 :
					return result.getTransferAt( rowIndex - sum ).getName();
				case 2 :
					return Arrays.toString( result.getTransferredValuesAt( rowIndex - sum ) );
				}
			}

			return null;
		}

	}

	private class InternalTestRunListener extends TestRunListenerAdapter
	{
		@Override
		public void afterStep( TestCaseRunner testRunner, TestCaseRunContext runContext, TestStepResult result )
		{
			if( result.getTestStep() == transferStep )
			{
				transferLogTableModel.addResult( ( PropertyTransferResult )result );
			}
		}
	}

	private class StepComboRenderer extends DefaultListCellRenderer
	{
		@Override
		public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected,
				boolean cellHasFocus )
		{
			Component result = super.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus );

			if( value instanceof TestModelItem )
			{
				TestModelItem item = ( TestModelItem )value;
				setIcon( item.getIcon() );
				setText( item.getName() );
			}
			else if( value == PropertyExpansionUtils.getGlobalProperties() )
			{
				setText( "Global" );
			}

			setToolTipText( getText() );

			return result;
		}
	}

	private class PropertyComboRenderer extends DefaultListCellRenderer
	{
		@Override
		public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected,
				boolean cellHasFocus )
		{
			Component result = super.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus );

			if( value != null )
			{
				TestProperty item = ( TestProperty )value;
				setText( item.getName() );
			}

			setToolTipText( getText() );

			return result;
		}
	}
}
