You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@taverna.apache.org by st...@apache.org on 2015/03/06 23:34:44 UTC

[48/50] [abbrv] incubator-taverna-workbench git commit: taverna-*

taverna-*


Project: http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/commit/fb641cfc
Tree: http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/tree/fb641cfc
Diff: http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/diff/fb641cfc

Branch: refs/heads/master
Commit: fb641cfc7feff5907b90301193a9b2aca45e8333
Parents: f676ef3
Author: Stian Soiland-Reyes <st...@apache.org>
Authored: Fri Mar 6 22:28:51 2015 +0000
Committer: Stian Soiland-Reyes <st...@apache.org>
Committed: Fri Mar 6 22:28:51 2015 +0000

----------------------------------------------------------------------
 .travis.yml                                     |   1 -
 taverna-uibuilder/pom.xml                       |  26 +
 .../lang/uibuilder/AbstractListComponent.java   | 521 +++++++++++++++++++
 .../t2/lang/uibuilder/AlignableComponent.java   |  29 ++
 .../sf/taverna/t2/lang/uibuilder/Alignment.java |  66 +++
 .../taverna/t2/lang/uibuilder/BeanCheckBox.java |  54 ++
 .../t2/lang/uibuilder/BeanComponent.java        | 420 +++++++++++++++
 .../t2/lang/uibuilder/BeanEnumComboBox.java     |  89 ++++
 .../taverna/t2/lang/uibuilder/BeanTextArea.java |  89 ++++
 .../t2/lang/uibuilder/BeanTextComponent.java    | 174 +++++++
 .../t2/lang/uibuilder/BeanTextField.java        |  60 +++
 .../net/sf/taverna/t2/lang/uibuilder/Icons.java |  41 ++
 .../taverna/t2/lang/uibuilder/ListHandler.java  |  67 +++
 .../lang/uibuilder/RecursiveListComponent.java  |  98 ++++
 .../sf/taverna/t2/lang/uibuilder/UIBuilder.java | 236 +++++++++
 .../lang/uibuilder/UIConstructionException.java |  29 ++
 .../t2/lang/uibuilder/WrappedListComponent.java | 105 ++++
 .../sf/taverna/t2/lang/uibuilder/package.html   |   4 +
 .../net/sf/taverna/t2/lang/uibuilder/delete.png | Bin 0 -> 1120 bytes
 .../net/sf/taverna/t2/lang/uibuilder/down.png   | Bin 0 -> 1077 bytes
 .../net/sf/taverna/t2/lang/uibuilder/new.png    | Bin 0 -> 1146 bytes
 .../net/sf/taverna/t2/lang/uibuilder/up.png     | Bin 0 -> 1074 bytes
 .../taverna/t2/lang/uibuilder/Application.java  |  48 ++
 .../taverna/t2/lang/uibuilder/Application2.java |  44 ++
 .../t2/lang/uibuilder/BeanWithBoundProps.java   |  74 +++
 .../lang/uibuilder/BeanWithListProperties.java  |  35 ++
 .../t2/lang/uibuilder/BeanWithNestedList.java   |  28 +
 .../t2/lang/uibuilder/PrimitiveTypeBean.java    | 101 ++++
 .../taverna/t2/lang/uibuilder/SampleEnum.java   |  13 +
 .../taverna/t2/lang/uibuilder/TopLevelBean.java |  43 ++
 uibuilder/pom.xml                               |  26 -
 .../lang/uibuilder/AbstractListComponent.java   | 521 -------------------
 .../t2/lang/uibuilder/AlignableComponent.java   |  29 --
 .../sf/taverna/t2/lang/uibuilder/Alignment.java |  66 ---
 .../taverna/t2/lang/uibuilder/BeanCheckBox.java |  54 --
 .../t2/lang/uibuilder/BeanComponent.java        | 420 ---------------
 .../t2/lang/uibuilder/BeanEnumComboBox.java     |  89 ----
 .../taverna/t2/lang/uibuilder/BeanTextArea.java |  89 ----
 .../t2/lang/uibuilder/BeanTextComponent.java    | 174 -------
 .../t2/lang/uibuilder/BeanTextField.java        |  60 ---
 .../net/sf/taverna/t2/lang/uibuilder/Icons.java |  41 --
 .../taverna/t2/lang/uibuilder/ListHandler.java  |  67 ---
 .../lang/uibuilder/RecursiveListComponent.java  |  98 ----
 .../sf/taverna/t2/lang/uibuilder/UIBuilder.java | 236 ---------
 .../lang/uibuilder/UIConstructionException.java |  29 --
 .../t2/lang/uibuilder/WrappedListComponent.java | 105 ----
 .../sf/taverna/t2/lang/uibuilder/package.html   |   4 -
 .../net/sf/taverna/t2/lang/uibuilder/delete.png | Bin 1120 -> 0 bytes
 .../net/sf/taverna/t2/lang/uibuilder/down.png   | Bin 1077 -> 0 bytes
 .../net/sf/taverna/t2/lang/uibuilder/new.png    | Bin 1146 -> 0 bytes
 .../net/sf/taverna/t2/lang/uibuilder/up.png     | Bin 1074 -> 0 bytes
 .../taverna/t2/lang/uibuilder/Application.java  |  48 --
 .../taverna/t2/lang/uibuilder/Application2.java |  44 --
 .../t2/lang/uibuilder/BeanWithBoundProps.java   |  74 ---
 .../lang/uibuilder/BeanWithListProperties.java  |  35 --
 .../t2/lang/uibuilder/BeanWithNestedList.java   |  28 -
 .../t2/lang/uibuilder/PrimitiveTypeBean.java    | 101 ----
 .../taverna/t2/lang/uibuilder/SampleEnum.java   |  13 -
 .../taverna/t2/lang/uibuilder/TopLevelBean.java |  43 --
 59 files changed, 2494 insertions(+), 2495 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/.travis.yml
----------------------------------------------------------------------
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index dff5f3a..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1 +0,0 @@
-language: java

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/pom.xml
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/pom.xml b/taverna-uibuilder/pom.xml
new file mode 100644
index 0000000..bc84391
--- /dev/null
+++ b/taverna-uibuilder/pom.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>net.sf.taverna.t2</groupId>
+		<artifactId>lang</artifactId>
+		<version>2.0.1-SNAPSHOT</version>
+	</parent>
+	<groupId>net.sf.taverna.t2.lang</groupId>
+	<artifactId>uibuilder</artifactId>
+	<packaging>bundle</packaging>
+	<name>UI builder based on beans</name>
+	<dependencies>
+		<dependency>
+			<groupId>net.sf.taverna.t2.lang</groupId>
+			<artifactId>ui</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.log4j</groupId>
+			<artifactId>com.springsource.org.apache.log4j</artifactId>
+			<version>${log4j.version}</version>
+		</dependency>
+	</dependencies>
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/AbstractListComponent.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/AbstractListComponent.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/AbstractListComponent.java
new file mode 100644
index 0000000..9a7761c
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/AbstractListComponent.java
@@ -0,0 +1,521 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import static net.sf.taverna.t2.lang.uibuilder.Icons.getIcon;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.lang.reflect.InvocationTargetException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JSeparator;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Superclass for list and wrapped list components generated by the UI builder.
+ * Accepts properties to determine whether the 'new', 'move' and 'delete'
+ * controls should be displayed and what, if any, concrete class should be used
+ * when constructing new items. Properties are as follows:
+ * <p>
+ * <ul>
+ * <li><code>nodelete</code> If set do not show the 'delete item' button</li>
+ * <li><code>nomove</code> If set do not show the 'move up' and 'move down'
+ * buttons</li>
+ * <li><code>new=some.class.Name</code> If set then use the specified class,
+ * loaded with the target object's classloader, when adding new items. Also adds
+ * the 'New Item' button to the top of the list panel. The specified class must
+ * be valid to insert into the list and must have a no-argument constructor</li>
+ * </ul>
+ * 
+ * @author Tom Oinn
+ * 
+ */
+public abstract class AbstractListComponent extends JPanel {
+
+	private static final long serialVersionUID = 2067559836729348490L;
+
+	private static Logger logger = Logger
+	.getLogger(AbstractListComponent.class);
+
+	private String fieldName;
+	@SuppressWarnings("unchecked")
+	private List theList;
+	private Properties props;
+	private Map<String, Properties> fieldProps;
+	private List<String> subFields;
+	private String parent;
+	private JPanel listItemContainer;
+	private boolean showDelete = true;
+	private boolean showMove = true;
+	@SuppressWarnings("unchecked")
+	private Class newItemClass = null;
+	protected static Color deferredButtonColour = Color.orange;
+	private static Object[] prototypes;
+
+	static {
+		try {
+			prototypes = new Object[] { new URL("http://some.host.com/path"),
+					new Boolean(true), new Integer(1), new Float(1),
+					new Short((short) 1), new Long((long) 1),
+					new Character('a'), new Double((double) 1),
+					new Byte((byte) 1) };
+		} catch (MalformedURLException e) {
+			logger.error("Unable to generate URL", e);
+		}
+	}
+
+	/**
+	 * Build a generic list component
+	 * 
+	 * @param fieldName
+	 *            the name of the field this component represents in its parent
+	 *            bean
+	 * @param theList
+	 *            the list value of the field
+	 * @param props
+	 *            properties for this field
+	 * @param fieldProps
+	 *            aggregated properties for all fields in the UI builder scope
+	 * @param newItemClass
+	 *            the class to use for construction of new item instances, or
+	 *            null if this is not supported
+	 * @param subFields
+	 *            all field names within this field, this will be empty for a
+	 *            wrapped list
+	 * @param parent
+	 *            the parent field ID used to access subfield properties
+	 * @throws NoSuchMethodException
+	 * @throws ClassNotFoundException
+	 * @throws InvocationTargetException
+	 * @throws IllegalAccessException
+	 * @throws IllegalArgumentException
+	 */
+	@SuppressWarnings("unchecked")
+	protected AbstractListComponent(String fieldName, List theList,
+			Properties props, Map<String, Properties> fieldProps,
+			final Class<?> newItemClass, List<String> subFields, String parent)
+			throws NoSuchMethodException, IllegalArgumentException,
+			IllegalAccessException, InvocationTargetException,
+			ClassNotFoundException {
+		super();
+		this.fieldName = fieldName;
+		this.theList = theList;
+		this.props = props;
+		this.fieldProps = fieldProps;
+		this.subFields = subFields;
+		this.parent = parent;
+		if (props.containsKey("nodelete")) {
+			showDelete = false;
+		}
+		if (props.containsKey("nomove")) {
+			showMove = false;
+		}
+		this.newItemClass = newItemClass;
+		setOpaque(false);
+		setLayout(new BorderLayout());
+		String displayName = fieldName;
+		if (props.containsKey("name")) {
+			displayName = props.getProperty("name");
+		}
+		setBorder(BorderFactory.createTitledBorder(displayName));
+		if (newItemClass != null) {
+			// Generate 'add new' UI here
+			JPanel newItemPanel = new JPanel();
+			newItemPanel.setOpaque(false);
+			newItemPanel.setLayout(new BoxLayout(newItemPanel,
+					BoxLayout.LINE_AXIS));
+			newItemPanel.add(Box.createHorizontalGlue());
+
+			final JButton newItemButton = new JButton("New Item",
+					getIcon("new"));
+			newItemPanel.add(newItemButton);
+			newItemButton.addActionListener(new ActionListener() {
+				public void actionPerformed(ActionEvent e) {
+					try {
+						Object instance = null;
+						try {
+							instance = newItemClass.newInstance();
+						} catch (InstantiationException ie) {
+							// Probably because the class has no default
+							// constructor, use the prototype list
+							for (Object prototype : prototypes) {
+								if (newItemClass.isAssignableFrom(prototype
+										.getClass())) {
+									instance = prototype;
+									break;
+								}
+							}
+							if (instance == null) {
+								throw ie;
+							}
+						}
+						addNewItemToList(instance);
+						newItemButton.setForeground(BeanComponent.validColour);
+					} catch (Exception ex) {
+						newItemButton
+								.setForeground(BeanComponent.invalidColour);
+						logger.error("", ex);
+					}
+				}
+			});
+			add(newItemPanel, BorderLayout.NORTH);
+		}
+		listItemContainer = new JPanel();
+		listItemContainer.setOpaque(false);
+		listItemContainer.setLayout(new BoxLayout(listItemContainer,
+				BoxLayout.PAGE_AXIS));
+		updateListContents();
+		add(listItemContainer, BorderLayout.CENTER);
+	}
+
+	protected void updateListContents() throws NoSuchMethodException,
+			IllegalArgumentException, IllegalAccessException,
+			InvocationTargetException, ClassNotFoundException {
+		listItemContainer.removeAll();
+		boolean first = true;
+		int index = 0;
+		List<JComponent> listComponents = getListComponents();
+		for (JComponent component : listComponents) {
+			if (first && newItemClass == null) {
+				first = false;
+			} else {
+				List<Component> c = getSeparatorComponents();
+				if (c != null) {
+					for (Component jc : c) {
+						listItemContainer.add(jc);
+					}
+				}
+			}
+			JComponent wrappedComponent = wrapWithControls(component, index++,
+					listComponents.size());
+			if (wrappedComponent != null) {
+				listItemContainer.add(wrappedComponent);
+			} else {
+				listItemContainer.add(component);
+			}
+		}
+		revalidate();
+	}
+
+	/**
+	 * Wrap the given component in a panel including whatever list manipulation
+	 * controls are needed. The index of the item being wrapped is supplied to
+	 * inform the various actions the controls can perform. By default this
+	 * returns a JPanel with delete and move controls
+	 * 
+	 * @param component
+	 * @param index
+	 * @return
+	 */
+	protected JPanel wrapWithControls(JComponent component, final int index,
+			int listSize) {
+		int numberOfButtons = 3;
+		if (!showDelete) {
+			numberOfButtons--;
+		}
+		if (!showMove) {
+			numberOfButtons -= 2;
+		}
+		if (numberOfButtons == 0) {
+			return null;
+		}
+		JPanel result = new JPanel();
+		result.setOpaque(false);
+		result.setLayout(new BorderLayout());
+		result.add(component, BorderLayout.CENTER);
+		// Construct the controls
+		JPanel controls = new JPanel();
+		controls.setOpaque(false);
+		controls.setLayout(new BorderLayout());
+		controls
+				.add(new JSeparator(SwingConstants.VERTICAL), BorderLayout.WEST);
+		result.add(controls, BorderLayout.EAST);
+		JPanel buttons = new JPanel();
+		buttons.setOpaque(false);
+		buttons.setLayout(new GridLayout(0, numberOfButtons));
+
+		if (showMove) {
+			// Move up button, or spacer if already at index 0
+			if (index > 0) {
+				JButton moveUpButton = createButton("up", false);
+				moveUpButton.addActionListener(new ActionListener() {
+					public void actionPerformed(ActionEvent e) {
+						try {
+							itemMoved(index, index - 1);
+						} catch (Exception ex) {
+							logger.error("Unable to move item", ex);
+						}
+					}
+				});
+				buttons.add(moveUpButton);
+			} else {
+				buttons.add(Box.createGlue());
+			}
+
+			// Move down button, or spacer if index == listSize-1
+			if (index < (listSize - 1)) {
+				JButton moveDownButton = createButton("down", false);
+				moveDownButton.addActionListener(new ActionListener() {
+					public void actionPerformed(ActionEvent e) {
+						try {
+							itemMoved(index, index + 1);
+						} catch (Exception ex) {
+							logger.error("Unable to move item", ex);
+						}
+					}
+				});
+				buttons.add(moveDownButton);
+			} else {
+				buttons.add(Box.createGlue());
+			}
+		}
+
+		if (showDelete) {
+			// Delete button
+			JButton deleteButton = createButton("delete", true);
+			deleteButton.addActionListener(new ActionListener() {
+				public void actionPerformed(ActionEvent e) {
+					try {
+						deleteItemAtIndex(index);
+					} catch (Exception ex) {
+						logger.error("Unable to delete item", ex);
+					}
+				}
+			});
+			buttons.add(deleteButton);
+		}
+		JPanel buttonWrapper = new JPanel();
+		buttonWrapper.setLayout(new BorderLayout());
+		buttonWrapper.setOpaque(false);
+		buttonWrapper.add(buttons, BorderLayout.NORTH);
+		controls.add(buttonWrapper, BorderLayout.CENTER);
+		return result;
+	}
+
+	private static Timer timer = new Timer();
+
+	@SuppressWarnings("serial")
+	private JButton createButton(String iconName, final boolean deferActions) {
+		JButton result = new JButton(getIcon(iconName)) {
+			@Override
+			public Dimension getPreferredSize() {
+				return new Dimension(getIcon().getIconWidth() + 8, (int) super
+						.getPreferredSize().getHeight());
+			}
+
+			@Override
+			public Dimension getMinimumSize() {
+				return new Dimension(getIcon().getIconWidth() + 8, (int) super
+						.getMinimumSize().getHeight());
+			}
+
+			private boolean active = false;
+			private Color defaultBackground = null;
+
+			@Override
+			public void addActionListener(final ActionListener theListener) {
+				if (defaultBackground == null) {
+					defaultBackground = getBackground();
+				}
+				if (!deferActions) {
+					super.addActionListener(theListener);
+					return;
+				} else {
+					super.addActionListener(new ActionListener() {
+						public void actionPerformed(ActionEvent ae) {
+							if (active) {
+								theListener.actionPerformed(ae);
+							} else {
+								setActive(true);
+								timer.schedule(new TimerTask() {
+
+									@Override
+									public void run() {
+										SwingUtilities
+												.invokeLater(new Runnable() {
+													public void run() {
+														setActive(false);
+													}
+												});
+									}
+								}, 1000);
+							}
+						}
+					});
+				}
+			}
+
+			private synchronized void setActive(boolean isActive) {
+				if (isActive == active) {
+					return;
+				} else {
+					active = isActive;
+					setBackground(active ? deferredButtonColour
+							: defaultBackground);
+
+				}
+			}
+		};
+		result.setFocusable(false);
+		return result;
+	}
+
+	/**
+	 * Called when building the UI, must return a list of editor components
+	 * corresponding to items in the list
+	 * 
+	 * @throws NoSuchMethodException
+	 * @throws ClassNotFoundException
+	 * @throws InvocationTargetException
+	 * @throws IllegalAccessException
+	 * @throws IllegalArgumentException
+	 */
+	protected abstract List<JComponent> getListComponents()
+			throws NoSuchMethodException, IllegalArgumentException,
+			IllegalAccessException, InvocationTargetException,
+			ClassNotFoundException;
+
+	/**
+	 * Override to specify a separator component to be used inbetween internal
+	 * list components, by default no component is used (this returns null).
+	 * 
+	 * @return
+	 */
+	protected List<Component> getSeparatorComponents() {
+		return null;
+	}
+
+	/**
+	 * Called when the user has clicked on the 'new item' button, this method is
+	 * passed the new instance of the specified type and should handle the
+	 * addition of this item to the list and the update of the UI to reflect
+	 * this change
+	 * 
+	 * @param o
+	 *            the object to add to the list
+	 * @throws ClassNotFoundException
+	 * @throws InvocationTargetException
+	 * @throws IllegalAccessException
+	 * @throws NoSuchMethodException
+	 * @throws IllegalArgumentException
+	 */
+	protected abstract void addNewItemToList(Object o)
+			throws IllegalArgumentException, NoSuchMethodException,
+			IllegalAccessException, InvocationTargetException,
+			ClassNotFoundException;
+
+	/**
+	 * Called when the user has clicked on the 'delete item' button next to a
+	 * particular item in the list, this method is passed the index within the
+	 * list of the item to be deleted and must update the UI appropriately
+	 * 
+	 * @param index
+	 * @throws ClassNotFoundException
+	 * @throws InvocationTargetException
+	 * @throws IllegalAccessException
+	 * @throws NoSuchMethodException
+	 * @throws IllegalArgumentException
+	 */
+	protected abstract void deleteItemAtIndex(int index)
+			throws IllegalArgumentException, NoSuchMethodException,
+			IllegalAccessException, InvocationTargetException,
+			ClassNotFoundException;
+
+	/**
+	 * Called when the user has moved an item from one index to another, passed
+	 * the old index of the item and the desired new index. This method must
+	 * effect the actual move within the list and the update of the UI.
+	 * 
+	 * @throws ClassNotFoundException
+	 * @throws InvocationTargetException
+	 * @throws IllegalAccessException
+	 * @throws NoSuchMethodException
+	 * @throws IllegalArgumentException
+	 */
+	protected abstract void itemMoved(int fromIndex, int toIndex)
+			throws IllegalArgumentException, NoSuchMethodException,
+			IllegalAccessException, InvocationTargetException,
+			ClassNotFoundException;
+
+	/**
+	 * Get the field name of this component
+	 * 
+	 * @return
+	 */
+	protected final String getFieldName() {
+		return this.fieldName;
+	}
+
+	/**
+	 * Return the underlying list presented by this component
+	 * 
+	 * @return
+	 */
+	@SuppressWarnings("unchecked")
+	protected final List getUnderlyingList() {
+		return this.theList;
+	}
+
+	/**
+	 * Get a list of (renamed) sub-fields of this field, so if this field was
+	 * foo.bar.someList and had a foo.bar.someList.urgle this would contain
+	 * 'urgle'
+	 * 
+	 * @return
+	 */
+	protected final List<String> getSubFields() {
+		return this.subFields;
+	}
+
+	/**
+	 * Get the properties applied to this list component
+	 * 
+	 * @return
+	 */
+	protected final Properties getProperties() {
+		return this.props;
+	}
+
+	/**
+	 * The parent field name is the name of this field plus its parent string
+	 * and is used when recursively building sub-panels within the list. Pass
+	 * this into the 'parent' argument of the UIBuilder's construction methods.
+	 * 
+	 * @return
+	 */
+	protected final String getParentFieldName() {
+		return this.parent;
+	}
+
+	/**
+	 * Get the map of all field->property object block defined by the UI builder
+	 * constructing this component. This is used along with the parent field
+	 * name to determine properties of sub-components when recursing into a
+	 * nested collection.
+	 * 
+	 * @return
+	 */
+	protected final Map<String, Properties> getFieldProperties() {
+		return this.fieldProps;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/AlignableComponent.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/AlignableComponent.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/AlignableComponent.java
new file mode 100644
index 0000000..483771b
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/AlignableComponent.java
@@ -0,0 +1,29 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+/**
+ * Superinterface for components which have a label and which may be mutually
+ * aligned within a panel. This assumes the component is laid out with a label
+ * to the left of the main editing area, and that we want to ensure that all
+ * editing areas line up and can do this by setting the preferred size of the
+ * label.
+ * 
+ * @author Tom Oinn
+ * 
+ */
+public interface AlignableComponent {
+
+	/**
+	 * Set the preferred width of the label for this alignable component
+	 * 
+	 * @param newWidth
+	 */
+	public void setLabelWidth(int newWidth);
+
+	/**
+	 * Get the current preferred width of the label for this alignable component
+	 * 
+	 * @return
+	 */
+	public int getLabelWidth();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/Alignment.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/Alignment.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/Alignment.java
new file mode 100644
index 0000000..6143887
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/Alignment.java
@@ -0,0 +1,66 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.SwingUtilities;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Static utility method to align alignable components within a container
+ * 
+ * @author Tom Oinn
+ * 
+ */
+public abstract class Alignment {
+
+	private static Logger logger = Logger
+	.getLogger(Alignment.class);
+
+	/**
+	 * Find all instances of BeanFieldTextArea in the specified container and
+	 * set all label widths to the same as the widest of them, aligning the
+	 * content areas as a result
+	 * 
+	 * @param container
+	 */
+	public static void alignInContainer(Container container) {
+		int widestLabel = 0;
+		final List<AlignableComponent> fields = new ArrayList<AlignableComponent>();
+		for (Component comp : container.getComponents()) {
+			if (comp instanceof AlignableComponent) {
+				AlignableComponent field = (AlignableComponent) comp;
+				int fieldWidth = field.getLabelWidth();
+				if (fieldWidth > widestLabel) {
+					widestLabel = fieldWidth;
+				}
+				fields.add(field);
+			}
+		}
+		final int widestLabelVal = widestLabel;
+		if (!SwingUtilities.isEventDispatchThread()) {
+			try {
+				SwingUtilities.invokeAndWait(new Runnable() {
+					public void run() {
+						for (AlignableComponent field : fields) {
+							field.setLabelWidth(widestLabelVal);
+						}
+					}
+				});
+			} catch (InterruptedException e) {
+				logger.error("", e);
+			} catch (InvocationTargetException e) {
+				logger.error("", e);
+			}
+		} else {
+			for (AlignableComponent field : fields) {
+				field.setLabelWidth(widestLabelVal);
+			}
+		}
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanCheckBox.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanCheckBox.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanCheckBox.java
new file mode 100644
index 0000000..1d86f4a
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanCheckBox.java
@@ -0,0 +1,54 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Properties;
+
+import javax.swing.Box;
+import javax.swing.JCheckBox;
+
+/**
+ * Bean field editor using a JCheckBox to handle boolean and Boolean field types
+ * 
+ * @author Tom Oinn
+ */
+public class BeanCheckBox extends BeanComponent implements AlignableComponent {
+
+	private static final long serialVersionUID = -2842617445268734650L;
+	private JCheckBox value;
+
+	public BeanCheckBox(Object target, String propertyName, Properties props)
+			throws NoSuchMethodException {
+		this(target, propertyName, true, props);
+	}
+
+	public BeanCheckBox(Object target, String propertyName, boolean useLabel,
+			Properties props) throws NoSuchMethodException {
+		super(target, propertyName, useLabel, props);
+		setLayout(new BorderLayout());
+		value = new JCheckBox();
+		value.setSelected(getBooleanProperty());
+		value.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent ae) {
+				boolean isSelected = value.isSelected();
+				currentObjectValue = isSelected;
+				setProperty();
+			}
+		});
+		addLabel();
+		value.setOpaque(false);
+		add(Box.createHorizontalGlue(), BorderLayout.CENTER);
+		add(value, BorderLayout.EAST);
+	}
+
+	@Override
+	protected void updateComponent() {
+		value.setSelected(getBooleanProperty());
+	}
+
+	private boolean getBooleanProperty() {
+		return (Boolean) getProperty();
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanComponent.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanComponent.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanComponent.java
new file mode 100644
index 0000000..27014e1
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanComponent.java
@@ -0,0 +1,420 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Properties;
+
+import javax.swing.Box;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+import javax.swing.event.AncestorEvent;
+import javax.swing.event.AncestorListener;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Superclass of all bean component editors acting on a single (non collection)
+ * property within a POJO. This handles bound properties, using reflection to
+ * determine whether an appropriate addPropertyChangeListener method exists on
+ * the target bean and registering a listener if so. It also registers an
+ * ancestor listener to de-register this property listener if the component is
+ * removed from the display heirarchy and to re-attach it if the component is
+ * added. Implement updateComponent with the code to update the UI state from
+ * the underlying object, this is called when a property change is received by
+ * the listener (but not used for unbound properties).
+ * <p>
+ * This superclass also defines the name property:
+ * <ul>
+ * <li><code>name=SomeName</code> by default field labels use the field name
+ * defined in the configuration, including any case used. If this property is
+ * defined then the specified name is used instead</li>
+ * </ul>
+ * 
+ * @author Tom Oinn
+ */
+public abstract class BeanComponent extends JPanel {
+
+	private static Logger logger = Logger
+	.getLogger(BeanComponent.class);
+
+	private static final long serialVersionUID = -6044009506938335937L;
+	protected Object target;
+	protected String propertyName;
+	protected Method getMethod, setMethod = null;
+	protected boolean editable = true;
+	protected Class<?> propertyType;
+	protected static Color invalidColour = Color.red;
+	protected static Color validColour = Color.black;
+	protected static Color uneditedColour = Color.white;
+	protected static Color editedColour = new Color(255, 245, 200);
+	protected boolean currentValueValid = true;
+	protected Object currentObjectValue = null;
+	protected JLabel label;
+	private boolean useLabel = false;
+	protected static int height = 16;
+	private Properties properties;
+	private PropertyChangeListener propertyListener = null;
+
+	/**
+	 * Equivalent to BeanComponent(target, propertyName, true, props)
+	 */
+	public BeanComponent(Object target, String propertyName, Properties props)
+			throws NoSuchMethodException {
+		this(target, propertyName, true, props);
+	}
+
+	/**
+	 * Superclass constructor for BeanComponent instances.
+	 * 
+	 * @param target
+	 *            the object containing the property this component acts as a
+	 *            view and controller for
+	 * @param propertyName
+	 *            name of the property in the target object
+	 * @param useLabel
+	 *            whether to show the label component (we set this to false for
+	 *            wrapped lists, for example)
+	 * @param props
+	 *            a component specific properties object, passing in any
+	 *            properties defined in the configuration passed to UIBuilder
+	 * @throws NoSuchMethodException
+	 *             if the appropriate get method for the named property can't be
+	 *             found
+	 */
+	public BeanComponent(Object target, String propertyName, boolean useLabel,
+			Properties props) throws NoSuchMethodException {
+		super();
+		setOpaque(false);
+		// Find methods
+		this.properties = props;
+		this.useLabel = useLabel;
+		this.target = target;
+		this.propertyName = propertyName;
+
+		// If the target implements property change support then we can attach a
+		// listener to update the UI if the bound property changes. This
+		// listener is attached and detached in response to an ancestor listener
+		// so the bound property is only monitored when the component is visible
+		// in the UI.
+		try {
+			target.getClass().getMethod("addPropertyChangeListener",
+					String.class, PropertyChangeListener.class);
+			setUpPropertyListener();
+			addAncestorListener(new AncestorListener() {
+				public void ancestorAdded(AncestorEvent event) {
+					setUpPropertyListener();
+				}
+
+				public void ancestorMoved(AncestorEvent event) {
+					// Ignore
+				}
+
+				public void ancestorRemoved(AncestorEvent event) {
+					tearDownPropertyListener();
+				}
+			});
+		} catch (NoSuchMethodException nsme) {
+			// Means we don't have a bound property listener
+		}
+
+		getMethod = findMethodWithPrefix("get");
+		try {
+			setMethod = findMethodWithPrefix("set");
+		} catch (NoSuchMethodException nsme) {
+			logger.error("Unable to find set method", nsme);
+			editable = false;
+		}
+		propertyType = getPropertyType(target, propertyName);
+	}
+
+	/**
+	 * Attempts to create and bind a property change listener to the target
+	 * bean, failing silently if the bean doesn't implement the appropriate
+	 * methods
+	 */
+	private synchronized void setUpPropertyListener() {
+		if (propertyListener == null) {
+			Method addListener = null;
+			try {
+				addListener = target.getClass().getMethod(
+						"addPropertyChangeListener", String.class,
+						PropertyChangeListener.class);
+			} catch (NoSuchMethodException nsme) {
+				return;
+			}
+			propertyListener = new PropertyChangeListener() {
+				public void propertyChange(PropertyChangeEvent evt) {
+					Object newValue = evt.getNewValue();
+					if (currentObjectValue == null
+							|| (newValue != currentObjectValue && !newValue
+									.equals(currentObjectValue))) {
+						// System.out.println("Property change, source was "+evt.getSource());
+						if (SwingUtilities.isEventDispatchThread()) {
+							updateComponent();
+						} else {
+							// try {
+							SwingUtilities.invokeLater(new Runnable() {
+								public void run() {
+									updateComponent();
+								}
+							});
+						}
+					}
+				}
+			};
+			try {
+				addListener.invoke(target, propertyName, propertyListener);
+			} catch (IllegalArgumentException e) {
+				logger.error("Unable to set up property listener", e);
+			} catch (IllegalAccessException e) {
+				logger.error("Unable to set up property listener", e);
+			} catch (InvocationTargetException e) {
+				logger.error("Unable to set up property listener", e);
+			}
+		}
+	}
+
+	/**
+	 * If the property listener for bound properties exists then this method
+	 * unregisters it from the target, attempting to use a variety of the
+	 * standard method names to do so
+	 */
+	private synchronized void tearDownPropertyListener() {
+		if (propertyListener == null) {
+			return;
+		}
+		try {
+			Method removeListener = null;
+			try {
+				removeListener = target.getClass().getMethod(
+						"removePropertyChangeListener", String.class,
+						PropertyChangeListener.class);
+				removeListener.invoke(target, propertyName, propertyListener);
+			} catch (NoSuchMethodException nsme) {
+				try {
+					removeListener = target.getClass().getMethod(
+							"removePropertyChangeListener",
+							PropertyChangeListener.class);
+					removeListener.invoke(target, propertyListener);
+				} catch (NoSuchMethodException nsme2) {
+					return;
+				}
+			}
+		} catch (IllegalArgumentException e) {
+			logger.error("Unable to remove property listener", e);
+		} catch (IllegalAccessException e) {
+			logger.error("Unable to remove property listener", e);
+		} catch (InvocationTargetException e) {
+			logger.error("Unable to remove property listener", e);
+		}
+		propertyListener = null;
+	}
+
+	/**
+	 * Called by the bound property listener, implementing components must
+	 * update their UI state in this method to match the value of the underlying
+	 * component. This method is always called in the AWT dispatch thread so
+	 * implementations can safely modify the state of UI components without
+	 * worrying about swing thread handling
+	 */
+	protected abstract void updateComponent();
+
+	/**
+	 * Adds the label to the component if labels are enabled
+	 */
+	protected void addLabel() {
+		if (useLabel) {
+			String labelName = propertyName;
+			if (getProperties().containsKey("name")) {
+				labelName = getProperties().getProperty("name");
+			}
+			label = new JLabel(labelName);
+			label.setOpaque(false);
+			label.setPreferredSize(new Dimension(
+					label.getPreferredSize().width, height));
+			JPanel labelPanel = new JPanel();
+			labelPanel.setOpaque(false);
+			labelPanel.add(label);
+			labelPanel.add(Box.createHorizontalStrut(5));
+			add(labelPanel, BorderLayout.WEST);
+		}
+	}
+
+	/**
+	 * Return the type of the property on the target object, trying to locate a
+	 * matching 'get' method for the supplied property name and returning the
+	 * class of that method's return type.
+	 * <p>
+	 * Attempts to, in order :
+	 * <ol>
+	 * <li>Call the method and get the concrete type of the returned value</li>
+	 * <li>Get the declared return type of the get method</li>
+	 * </ol>
+	 * 
+	 * @param target
+	 * @param propertyName
+	 * @return
+	 * @throws NoSuchMethodException
+	 *             if the get method can't be found for the given property name
+	 *             on the target
+	 */
+	public static Class<?> getPropertyType(Object target, String propertyName)
+			throws NoSuchMethodException {
+		Method getMethod = findMethodWithPrefix("get", target, propertyName);
+		try {
+			Object value = getMethod.invoke(target);
+			if (value != null) {
+				return value.getClass();
+			}
+		} catch (InvocationTargetException ite) {
+			//
+		} catch (IllegalArgumentException e) {
+			// 			
+		} catch (IllegalAccessException e) {
+			// 
+		}
+		// if (target instanceof ListHandler.ListItem) {
+		// return ((ListHandler.ListItem) target).getTargetClass();
+		// }
+		return getMethod.getReturnType();
+	}
+
+	/**
+	 * Searches for the specified method on the target object, throwing an
+	 * exception if it can't be found. Searches for
+	 * target.[prefix][propertyname]()
+	 * 
+	 * @throws NoSuchMethodException
+	 */
+	static Method findMethodWithPrefix(String prefix, Object target,
+			String propertyName) throws NoSuchMethodException {
+		for (Method m : target.getClass().getMethods()) {
+			if (m.getName().equalsIgnoreCase(prefix + propertyName)) {
+				return m;
+			}
+		}
+		throw new NoSuchMethodException("Can't find method matching '" + prefix
+				+ propertyName + "' in " + target.getClass().getCanonicalName());
+	}
+
+	/**
+	 * Calls the static findMethodWithPrefix passing in the property name and
+	 * the target assigned to this instance
+	 * 
+	 * @param prefix
+	 *            a string prefix to use when finding the name, searches for
+	 *            [prefix][propertyname]()
+	 * @return a Method matching the query
+	 * @throws NoSuchMethodException
+	 *             if the method can't be found.
+	 */
+	protected final Method findMethodWithPrefix(String prefix)
+			throws NoSuchMethodException {
+		return findMethodWithPrefix(prefix, target, propertyName);
+	}
+
+	/**
+	 * Returns the toString value of the current bean property, or the empty
+	 * string if the get method returns null
+	 */
+	protected final String getPropertyAsString() {
+		Object value = getProperty();
+		if (value == null) {
+			return "";
+		}
+		currentObjectValue = value;
+		return value.toString();
+	}
+
+	/**
+	 * Uses reflection to call the get[property name] method on the target bean
+	 * and returns the result
+	 * 
+	 * @return current value of the bean property
+	 */
+	protected final Object getProperty() {
+		try {
+			Object value = getMethod.invoke(target);
+			return value;
+		} catch (Exception ex) {
+			logger.error("Unable to get property", ex);
+			return null;
+		}
+	}
+
+	/**
+	 * Sets the property on the object to the current object value for this UI
+	 * component, effectively pushing any changes into the underlying target
+	 * bean
+	 */
+	protected final void setProperty() {
+		if (currentValueValid && editable) {
+			if (setMethod != null) {
+				try {
+					setMethod.invoke(target, currentObjectValue);
+				} catch (IllegalArgumentException e) {
+					logger.error("Unable to set property", e);
+				} catch (IllegalAccessException e) {
+					logger.error("Unable to set property", e);
+				} catch (InvocationTargetException e) {
+					logger.error("Unable to set property", e);
+				}
+			}
+		}
+	}
+
+	/**
+	 * If using labels (as determined by the useLabel property) this sets the
+	 * preferred width of the label for this component
+	 * 
+	 * @param newWidth
+	 *            new label width in pixels
+	 */
+	public void setLabelWidth(int newWidth) {
+		if (useLabel) {
+			label.setPreferredSize(new Dimension(newWidth, height));
+		}
+	}
+
+	/**
+	 * If using labels (as determined by the useLabel property) this returns the
+	 * current preferred size of the label associated with this component
+	 * 
+	 * @return label width in pixels
+	 */
+	public int getLabelWidth() {
+		if (useLabel) {
+			return this.label.getPreferredSize().width;
+		} else {
+			return 0;
+		}
+	}
+
+	/**
+	 * If using the label then set its text (foreground) colour
+	 * 
+	 * @param colour
+	 */
+	public void setLabelColour(Color colour) {
+		if (useLabel) {
+			this.label.setForeground(colour);
+		}
+	}
+
+	/**
+	 * Get the properties object associated with this component. This is
+	 * generated from the configuration passed to UIBuilder
+	 * 
+	 * @return a Properties object containing named properties applying to this
+	 *         component in the UI
+	 */
+	public Properties getProperties() {
+		return this.properties;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanEnumComboBox.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanEnumComboBox.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanEnumComboBox.java
new file mode 100644
index 0000000..34646a4
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanEnumComboBox.java
@@ -0,0 +1,89 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Properties;
+
+import javax.swing.JComboBox;
+
+/**
+ * Bean property editor for enumerated property types, rendering the enumeration
+ * as a combo box
+ * 
+ * @author Tom Oinn
+ * 
+ */
+public class BeanEnumComboBox extends BeanComponent implements
+		AlignableComponent {
+
+	private static final long serialVersionUID = -6892016525599793149L;
+
+	private Object[] possibleValues;
+	private static int height = 24;
+	private JComboBox value;
+
+	public BeanEnumComboBox(Object target, String propertyName, Properties props)
+			throws NoSuchMethodException {
+		this(target, propertyName, true, props);
+	}
+
+	public BeanEnumComboBox(Object target, String propertyName,
+			boolean useLabel, Properties props) throws NoSuchMethodException {
+		super(target, propertyName, useLabel, props);
+		setLayout(new BorderLayout());
+		// Check that this is actually an enumeration type
+		if (!propertyType.isEnum()) {
+			throw new IllegalArgumentException(
+					"Can't use BeanEnumComboBox on a non Enumeration property");
+		}
+		possibleValues = propertyType.getEnumConstants();
+		value = new JComboBox(possibleValues) {
+
+			private static final long serialVersionUID = -7712225463703816146L;
+
+			@Override
+			public Dimension getMinimumSize() {
+				return new Dimension(super.getMinimumSize().width, height);
+			}
+
+			@Override
+			public Dimension getPreferredSize() {
+				return new Dimension(super.getPreferredSize().width, height);
+			}
+
+			@Override
+			public Dimension getMaximumSize() {
+				return new Dimension(super.getMaximumSize().width, height);
+			}
+		};
+		value.setSelectedIndex(currentValueIndex());
+		value.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				synchronized (this) {
+					currentObjectValue = value.getSelectedItem();
+					setProperty();
+				}
+			}
+		});
+		addLabel();
+		add(value, BorderLayout.CENTER);
+	}
+
+	private int currentValueIndex() {
+		Object currentValue = getProperty();
+		for (int i = 0; i < possibleValues.length; i++) {
+			if (currentValue.equals(possibleValues[i])) {
+				return i;
+			}
+		}
+		return -1;
+	}
+
+	@Override
+	protected void updateComponent() {
+		value.setSelectedIndex(currentValueIndex());
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextArea.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextArea.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextArea.java
new file mode 100644
index 0000000..740a592
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextArea.java
@@ -0,0 +1,89 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.Dimension;
+import java.awt.KeyboardFocusManager;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.swing.KeyStroke;
+import javax.swing.UIManager;
+import javax.swing.text.JTextComponent;
+
+import net.sf.taverna.t2.lang.ui.DialogTextArea;
+
+/**
+ * Bean editor based on a DialogTextArea for use with longer strings such as
+ * descriptions. Supports the 'nofilter' property, if this is not specified then
+ * the text inserted initially (but not on subsequent events such as property
+ * change messages) will be filtered to remove multiple whitespace elements,
+ * replacing them with spaces, and to trim leading and trailing whitespace.
+ * 
+ * @author Tom Oinn
+ * 
+ */
+public class BeanTextArea extends BeanTextComponent implements
+		AlignableComponent {
+
+	private static final long serialVersionUID = 6418526320837944375L;
+	boolean initialized = false;
+
+	public BeanTextArea(Object target, String propertyName, Properties props)
+			throws NoSuchMethodException {
+		super(target, propertyName, props);
+		initialized = true;
+	}
+
+	@SuppressWarnings( { "serial", "unchecked" })
+	@Override
+	protected JTextComponent getTextComponent() {
+		DialogTextArea result = new DialogTextArea() {
+			@Override
+			public void setText(String text) {
+				if (!initialized && !getProperties().containsKey("nofilter")) {
+					super.setText(text.replaceAll("[ \\t\\n\\x0B\\f\\r]+", " ")
+							.trim());
+				} else {
+					super.setText(text);
+				}
+			}
+
+			@Override
+			public Dimension getPreferredSize() {
+				return new Dimension(0, super.getPreferredSize().height);
+			}
+
+			@Override
+			public Dimension getMinimumSize() {
+				return new Dimension(0, super.getPreferredSize().height);
+			}
+		};
+		// Fix to add borders to DialogTextArea on old look and feel implementations,
+		// the new one (Nimbus) already has this
+		if (!UIManager.getLookAndFeel().getName().equals("Nimbus")) {
+			result.setBorder(UIManager.getBorder("TextField.border"));
+			result.setFont(UIManager.getFont("TextField.font"));
+		}
+		// Change tab behaviour to allow tab to move to the next field - this
+		// effectively prevents a tab being placed in the text area but hey, we
+		// don't really want people doing that anyway in these cases.
+		Set set = new HashSet(
+				result
+						.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
+		set.add(KeyStroke.getKeyStroke("TAB"));
+		result.setFocusTraversalKeys(
+				KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, set);
+
+		set = new HashSet(
+				result
+						.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS));
+		set.add(KeyStroke.getKeyStroke("shift TAB"));
+		result.setFocusTraversalKeys(
+				KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, set);
+
+		result.setLineWrap(true);
+		result.setWrapStyleWord(true);
+		return result;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextComponent.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextComponent.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextComponent.java
new file mode 100644
index 0000000..791f1a8
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextComponent.java
@@ -0,0 +1,174 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.BorderLayout;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.lang.reflect.Constructor;
+import java.util.Properties;
+
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.JTextComponent;
+
+/**
+ * Abstract superclass for all bean property editors based on text components
+ * 
+ * @author Tom Oinn
+ */
+public abstract class BeanTextComponent extends BeanComponent {
+
+	private static final long serialVersionUID = -9133735782847457090L;
+	private String regex = null;
+	private JTextComponent text;
+	private String originalValue = null;
+	private boolean edited = false;
+
+	protected BeanTextComponent(Object target, String propertyName,
+			Properties props) throws NoSuchMethodException {
+		this(target, propertyName, true, props);
+	}
+
+	protected BeanTextComponent(Object target, String propertyName,
+			boolean useLabel, Properties props) throws NoSuchMethodException {
+		super(target, propertyName, useLabel, props);
+		setLayout(new BorderLayout());
+		if (propertyType.equals(Boolean.class)) {
+			setRegex("\\A((true)|(false))\\Z");
+		} else if (propertyType.equals(Character.class)) {
+			setRegex("\\A.\\Z");
+		}
+		text = getTextComponent();
+		originalValue = getPropertyAsString();
+		text.setText(originalValue);
+		text.setEditable(editable);
+		add(text, BorderLayout.CENTER);
+		if (editable) {
+			text.getDocument().addDocumentListener(new DocumentListener() {
+				public void changedUpdate(DocumentEvent e) {
+					valueChangedInEditor();
+				}
+
+				public void insertUpdate(DocumentEvent e) {
+					valueChangedInEditor();
+				}
+
+				public void removeUpdate(DocumentEvent e) {
+					valueChangedInEditor();
+				}
+
+				private void valueChangedInEditor() {
+					BeanTextComponent.this.valueChangedInEditor();
+				}
+			});
+			text.addFocusListener(new FocusListener() {
+				public void focusGained(FocusEvent e) {
+					//
+				}
+
+				public void focusLost(FocusEvent e) {
+					// System.out.println("Focus lost : valid = "
+					// + currentValueValid);
+					if (currentValueValid) {
+						if (isEdited()) {
+							BeanTextComponent.this.setProperty();
+							originalValue = text.getText();
+						}
+					} else {
+						originalValue = getPropertyAsString();
+						text.setText(originalValue);
+					}
+					setEdited(false);
+				}
+
+			});
+		}
+		addLabel();
+	}
+
+	private boolean isEdited() {
+		return this.edited;
+	}
+
+	private void setEdited(boolean edited) {
+		if (edited == this.edited) {
+			return;
+		}
+		this.edited = edited;
+		text.setBackground(edited ? editedColour : uneditedColour);
+		if (!edited) {
+			setValid(true);
+		}
+	}
+
+	public void updateComponent() {
+		originalValue = getPropertyAsString();
+		text.setText(originalValue);
+		setEdited(false);
+	}
+
+	private void valueChangedInEditor() {
+		if (text.getText().equals(originalValue)) {
+			setEdited(false);
+			return;
+		}
+		setEdited(true);
+		// Check for regex
+		if (regex != null) {
+			if (text.getText().matches(regex) == false) {
+				// System.out.println(text.getText() + ".matches(" + regex
+				// + ")==false");
+				setValid(false);
+				return;
+			}
+		}
+		// Delegate to constructor for non-string classes
+		if (!propertyType.equals(String.class)) {
+			try {
+				Constructor<?> cons = null;
+				if (propertyType.equals(Character.class)
+						&& text.getText().length() > 0) {
+					currentObjectValue = text.getText().toCharArray()[0];
+				} else {
+					cons = propertyType.getConstructor(String.class);
+					currentObjectValue = cons.newInstance(text.getText());
+				}
+			} catch (Throwable t) {
+				setValid(false);
+				return;
+			}
+		} else {
+			currentObjectValue = text.getText();
+		}
+		setValid(true);
+	}
+
+	private void setValid(final boolean valid) {
+		if (valid == currentValueValid) {
+			return;
+		}
+		currentValueValid = valid;
+		text.setForeground(valid ? validColour : invalidColour);
+		setLabelColour(valid ? validColour : invalidColour);
+		text.repaint();
+	}
+
+	/**
+	 * Implement this to provide the actual UI component used to show the
+	 * content of the field. Done this way so you can choose what component to
+	 * use.
+	 * 
+	 * @return
+	 */
+	protected abstract JTextComponent getTextComponent();
+
+	/**
+	 * Set the regular expression used to validate the contents of the text
+	 * field
+	 * 
+	 * @param regex
+	 */
+	public void setRegex(String regex) {
+		this.regex = regex;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextField.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextField.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextField.java
new file mode 100644
index 0000000..63ed107
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/BeanTextField.java
@@ -0,0 +1,60 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Properties;
+
+import javax.swing.JTextField;
+import javax.swing.text.JTextComponent;
+
+/**
+ * Bean field editor using a JTextField for short values
+ * 
+ * @author Tom Oinn
+ */
+public class BeanTextField extends BeanTextComponent implements
+		AlignableComponent {
+
+	private static final long serialVersionUID = 5968203948656812060L;
+
+	public BeanTextField(Object target, String propertyName, boolean useLabel,
+			Properties props) throws NoSuchMethodException {
+		super(target, propertyName, useLabel, props);
+	}
+
+	public BeanTextField(Object target, String propertyName, Properties props)
+			throws NoSuchMethodException {
+		this(target, propertyName, true, props);
+	}
+
+	@SuppressWarnings("serial")
+	@Override
+	protected JTextComponent getTextComponent() {
+		final JTextField result = new JTextField() {
+
+			@Override
+			public Dimension getMinimumSize() {
+				return new Dimension(0, height);
+			}
+
+			@Override
+			public Dimension getPreferredSize() {
+				return new Dimension(0, height);
+			}
+
+			@Override
+			public Dimension getMaximumSize() {
+				return new Dimension(super.getMaximumSize().width, height);
+			}
+		};
+		result.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				setProperty();
+				result.transferFocus();
+			}
+		});
+		return new JTextField();
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/Icons.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/Icons.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/Icons.java
new file mode 100644
index 0000000..b8fc58e
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/Icons.java
@@ -0,0 +1,41 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.ImageIcon;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Manage icons for the UIBuilder
+ * 
+ * @author Tom Oinn
+ * 
+ */
+public abstract class Icons {
+
+	private static Logger logger = Logger
+	.getLogger(Icons.class);
+
+	private static Map<String, ImageIcon> icons;
+
+	static {
+		icons = new HashMap<String, ImageIcon>();
+	}
+
+	static synchronized ImageIcon getIcon(String iconName) {
+		String iconNameLC = iconName.toLowerCase();
+		if (!icons.containsKey(iconNameLC)) {
+			try {
+				URL iconURL = Icons.class.getResource(iconName + ".png");
+				icons.put(iconNameLC, new ImageIcon(iconURL));
+			} catch (Exception ex) {
+				logger.error("Unable to get icon resource", ex);
+			}
+		}
+		return icons.get(iconNameLC);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/ListHandler.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/ListHandler.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/ListHandler.java
new file mode 100644
index 0000000..e7a2db8
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/ListHandler.java
@@ -0,0 +1,67 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+/**
+ * The list handler is used to allow the reflection based UI builder to handle
+ * lists of non-bean value types such as String etc.
+ * 
+ * @author Tom Oinn
+ * 
+ */
+public class ListHandler extends
+		ArrayList<ListHandler.ListItem> {
+
+	private static Logger logger = Logger
+	.getLogger(ListHandler.class);
+
+	private static final long serialVersionUID = -1361470859975889856L;
+
+	private List<Object> wrappedList;
+	
+	public ListHandler(List<Object> theList) {
+		this.wrappedList = theList;
+		for (Object o : wrappedList) {
+			this.add(new ListItem(o));
+		}
+	}
+
+	/**@Override
+	public boolean add(ListHandler.ListItem newItem) {
+		wrappedList.add((T) newItem.getValue());
+		return super.add(newItem);
+	}*/
+
+	/**
+	 * Simple container class to handle list items, allowing them to present a
+	 * bean interface
+	 * 
+	 * @author Tom Oinn
+	 * 
+	 */
+	class ListItem {
+		Object value;
+
+		public ListItem(Object o) {
+			this.value = o;
+		}
+
+		public void setValue(Object o) {
+			try {
+			wrappedList.set(indexOf(this), o);
+			this.value = o;
+			}
+			catch (Exception ex) {
+				logger.error("Unable to set value", ex);
+			}
+		}
+				
+		public Object getValue() {
+			return this.value;
+		}
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/RecursiveListComponent.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/RecursiveListComponent.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/RecursiveListComponent.java
new file mode 100644
index 0000000..7db727d
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/RecursiveListComponent.java
@@ -0,0 +1,98 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.Component;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JSeparator;
+import javax.swing.SwingConstants;
+
+import net.sf.taverna.t2.lang.uibuilder.UIBuilder;
+
+/**
+ * Handles lists where the elements in the list are beans with declared fields
+ * in the UIBuilder configuration.
+ * 
+ * @author Tom Oinn
+ * 
+ */
+public class RecursiveListComponent extends AbstractListComponent {
+
+	private static final long serialVersionUID = -3760308074241973969L;
+
+	@SuppressWarnings("unchecked")
+	public RecursiveListComponent(String fieldName, List theList,
+			Properties props, Map<String, Properties> fieldProps,
+			Class<?> newItemClass, List<String> subFields, String parent)
+			throws NoSuchMethodException, IllegalArgumentException,
+			IllegalAccessException, InvocationTargetException,
+			ClassNotFoundException {
+		super(fieldName, theList, props, fieldProps, newItemClass, subFields,
+				parent);
+		// TODO Auto-generated constructor stub
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	protected void addNewItemToList(Object o) throws IllegalArgumentException,
+			NoSuchMethodException, IllegalAccessException,
+			InvocationTargetException, ClassNotFoundException {
+		getUnderlyingList().add(0, o);
+		updateListContents();
+	}
+
+	@Override
+	protected void deleteItemAtIndex(int index)
+			throws IllegalArgumentException, NoSuchMethodException,
+			IllegalAccessException, InvocationTargetException,
+			ClassNotFoundException {
+		getUnderlyingList().remove(index);
+		updateListContents();
+	}
+
+	@Override
+	protected List<JComponent> getListComponents()
+			throws NoSuchMethodException, IllegalArgumentException,
+			IllegalAccessException, InvocationTargetException,
+			ClassNotFoundException {
+		List<JComponent> result = new ArrayList<JComponent>();
+		for (Object target : getUnderlyingList()) {
+			JPanel listItem = new JPanel();
+			listItem.setOpaque(false);
+			listItem.setLayout(new BoxLayout(listItem, BoxLayout.PAGE_AXIS));
+			UIBuilder.buildEditor(target, getSubFields(), getFieldProperties(),
+					listItem, getParentFieldName());
+			result.add(listItem);
+		}
+		return result;
+	}
+
+	@Override
+	public List<Component> getSeparatorComponents() {
+		List<Component> result = new ArrayList<Component>();
+		result.add(Box.createVerticalStrut(3));
+		result.add(new JSeparator(SwingConstants.HORIZONTAL));
+		result.add(Box.createVerticalStrut(3));
+		return result;
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	protected void itemMoved(int fromIndex, int toIndex)
+			throws IllegalArgumentException, NoSuchMethodException,
+			IllegalAccessException, InvocationTargetException,
+			ClassNotFoundException {
+		Object toMove = getUnderlyingList().remove(fromIndex);
+		getUnderlyingList().add(toIndex, toMove);
+		updateListContents();
+
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/UIBuilder.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/UIBuilder.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/UIBuilder.java
new file mode 100644
index 0000000..2501046
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/UIBuilder.java
@@ -0,0 +1,236 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JPanel;
+
+/**
+ * Static methods to build bean editor UIs through reflection and (minimal)
+ * configuration
+ * 
+ * @author Tom Oinn
+ * 
+ */
+public abstract class UIBuilder {
+
+	/**
+	 * Build an editor component for the specified target, the configuration
+	 * string determines which properties are exposed to the UI and whether to
+	 * drill into nested collections and composite bean types
+	 * 
+	 * @param target
+	 * @param configuration
+	 * @return
+	 * @throws NoSuchMethodException
+	 * @throws InvocationTargetException
+	 * @throws IllegalAccessException
+	 * @throws IllegalArgumentException
+	 * @throws ClassNotFoundException
+	 */
+	public static JPanel buildEditor(Object target, String configuration)
+			throws UIConstructionException {
+		String[] fields = configuration.split(";");
+		return buildEditor(target, fields);
+	}
+
+	public static JPanel buildEditor(Object target, String[] fields)
+			throws UIConstructionException {
+		// Now go through the configuration...
+		JPanel result = new JPanel();
+		result.setLayout(new BorderLayout());
+		JPanel contents = new JPanel();
+		contents.setOpaque(false);
+		contents.setLayout(new BoxLayout(contents, BoxLayout.PAGE_AXIS));
+		List<String> fieldNames = new ArrayList<String>();
+		Map<String, Properties> fieldProps = new HashMap<String, Properties>();
+		for (String field : fields) {
+			String fieldName = field.split(":")[0];
+			fieldNames.add(fieldName);
+			if (field.split(":").length > 1) {
+				String propertiesString = field.split(":")[1];
+				String[] props = propertiesString.split(",");
+				if (props.length == 0) {
+					props = new String[] { propertiesString };
+				}
+				Properties properties = new Properties();
+				for (String prop : props) {
+					if (prop.contains("=")) {
+						String[] p = prop.split("=");
+						properties.put(p[0], p[1]);
+					} else {
+						properties.put(prop, "true");
+					}
+				}
+				fieldProps.put(fieldName, properties);
+			}
+		}
+		try {
+			buildEditor(target, fieldNames, fieldProps, contents, "");
+		} catch (Exception ex) {
+			throw new UIConstructionException(
+					"Unable to construct UI from POJO", ex);
+		}
+		result.add(contents, BorderLayout.NORTH);
+		result.add(Box.createVerticalGlue(), BorderLayout.CENTER);
+		return result;
+	}
+
+	@SuppressWarnings("unchecked")
+	static void buildEditor(Object target, List<String> fieldNames,
+			Map<String, Properties> fieldProps, Container contents,
+			String parent) throws NoSuchMethodException,
+			IllegalArgumentException, IllegalAccessException,
+			InvocationTargetException, ClassNotFoundException {
+
+		// Get all top level fields to render in this pass
+		List<String> activeFields = new ArrayList<String>();
+		for (String field : fieldNames) {
+			if (!field.contains(".")) {
+				activeFields.add(field);
+			}
+		}
+		// For each field generate the appropriate component
+		for (String field : activeFields) {
+
+			// Fetch the properties block for this field
+			Properties props = getProperties(field, parent, fieldProps);
+
+			// First check whether there are any subfields for this field, in
+			// which case we need to treat it differently
+			boolean simpleField = true;
+			for (String subField : fieldNames) {
+				if (!subField.equals(field) && subField.startsWith(field + ".")) {
+					simpleField = false;
+					break;
+				}
+			}
+			List<String> subFields = new ArrayList<String>();
+			// Create filtered list of the field names
+			// to ensure that this is now the top level
+			// field and recurse
+			String newParent = field;
+			if (!parent.equals("")) {
+				newParent = parent + "." + field;
+			}
+			for (String f : fieldNames) {
+				if (f.startsWith(field + ".")) {
+					subFields.add(f.substring(field.length() + 1));
+				}
+			}
+
+			// Secondly check whether this is a list
+			boolean listField = false;
+			Class<?> fieldType = BeanComponent.getPropertyType(target, field);
+			if (List.class.isAssignableFrom(fieldType)) {
+				listField = true;
+			}
+
+			// Now handle the four possible cases. If a non-list non-compound
+			// field we have a terminator and can obtain an appropriate subclass
+			// of BeanComponent to render it
+			if (!listField) {
+				// If a non-list non-compound field we have a terminator and can
+				// obtain an appropriate subclass of BeanComponent to render it
+				if (simpleField) {
+					if (fieldType.isEnum()) {
+						contents
+								.add(new BeanEnumComboBox(target, field, props));
+					} else if (Boolean.class.isAssignableFrom(fieldType)
+							|| boolean.class.isAssignableFrom(fieldType)) {
+						contents.add(new BeanCheckBox(target, field, props));
+					} else {
+						if (props.get("type") != null) {
+							if (props.get("type").equals("textarea")) {
+								BeanTextArea bta = new BeanTextArea(target,
+										field, props);
+								contents.add(bta);
+							} else {
+								contents.add(new BeanTextField(target, field,
+										props));
+							}
+						} else {
+							contents
+									.add(new BeanTextField(target, field, props));
+						}
+					}
+				} else {
+					Object value = BeanComponent.findMethodWithPrefix("get",
+							target, field).invoke(target);
+					if (value != null) {
+						String displayName = field;
+						if (props.containsKey("name")) {
+							displayName = props.getProperty("name");
+						}
+						JPanel itemContainer = new JPanel();
+						itemContainer.setOpaque(false);
+						itemContainer.setBorder(BorderFactory
+								.createTitledBorder(displayName));
+						itemContainer.setLayout(new BoxLayout(itemContainer,
+								BoxLayout.PAGE_AXIS));
+						buildEditor(value, subFields, fieldProps,
+								itemContainer, newParent);
+						contents.add(itemContainer);
+					}
+				}
+			} else {
+				// Handle the case where this is a simple field (i.e. no
+				// sub-fields defined) but is a list type. In this case we need
+				// to build an appropriate wrapper list panel around this
+				// returned list.
+				List value = (List) BeanComponent.findMethodWithPrefix("get",
+						target, field).invoke(target);
+				// If the 'new' property is defined then fetch the Class object
+				// to be used to instantiate new list items
+				Class<?> newItemClass = null;
+				if (props.containsKey("new")) {
+					String newItemClassName = props.getProperty("new");
+					newItemClass = target.getClass().getClassLoader()
+							.loadClass(newItemClassName);
+				}
+				if (value != null) {
+					if (simpleField) {
+						contents.add(new WrappedListComponent(field, value,
+								props, fieldProps, newItemClass, subFields,
+								newParent));
+						// contents.add(buildWrappedList(field, value, props,
+						// newItemClass));
+					} else {
+						contents.add(new RecursiveListComponent(field, value,
+								props, fieldProps, newItemClass, subFields,
+								newParent));
+						// contents.add(buildList(field, value, props,
+						// fieldProps,
+						// newItemClass, subFields, newParent));
+					}
+				}
+			}
+		}
+		// Finally align any labels where appropriate
+		Alignment.alignInContainer(contents);
+	}
+
+	private static Properties getProperties(String field, String parent,
+			Map<String, Properties> props) {
+		String fullName = parent;
+		if (!parent.equals("")) {
+			fullName = fullName + ".";
+		}
+		fullName = fullName + field;
+		if (props.containsKey(fullName)) {
+			return props.get(fullName);
+		} else {
+			return new Properties();
+		}
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/UIConstructionException.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/UIConstructionException.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/UIConstructionException.java
new file mode 100644
index 0000000..54167db
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/UIConstructionException.java
@@ -0,0 +1,29 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+/**
+ * Used to wrap checked exceptions from the various reflection based UI
+ * construction methods
+ * 
+ * @author Tom Oinn
+ */
+public class UIConstructionException extends RuntimeException {
+	
+	private static final long serialVersionUID = 3396809563793962316L;
+
+	public UIConstructionException() {
+		//
+	}
+
+	public UIConstructionException(String message) {
+		super(message);
+	}
+
+	public UIConstructionException(Throwable cause) {
+		super(cause);
+	}
+
+	public UIConstructionException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/WrappedListComponent.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/WrappedListComponent.java b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/WrappedListComponent.java
new file mode 100644
index 0000000..e44b036
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/WrappedListComponent.java
@@ -0,0 +1,105 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.swing.BoxLayout;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+/**
+ * Models lists requiring use of the ListHandler helper class within the UI
+ * 
+ * @author Tom Oinn
+ */
+public class WrappedListComponent extends AbstractListComponent {
+
+	private static final long serialVersionUID = -4457073442579747674L;
+	private ListHandler lh = null;
+
+	@SuppressWarnings("unchecked")
+	public WrappedListComponent(String fieldName, List theList,
+			Properties props, Map<String, Properties> fieldProps,
+			Class<?> newItemClass, List<String> subFields, String parent)
+			throws NoSuchMethodException, IllegalArgumentException,
+			IllegalAccessException, InvocationTargetException,
+			ClassNotFoundException {
+		super(fieldName, theList, props, fieldProps, newItemClass, subFields,
+				parent);
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	protected void addNewItemToList(Object o) throws IllegalArgumentException,
+			NoSuchMethodException, IllegalAccessException,
+			InvocationTargetException, ClassNotFoundException {
+		// Keep lists in sync
+		getListHandler();
+		synchronized (lh) {
+			getUnderlyingList().add(0, o);
+			lh.add(0, lh.new ListItem(o));
+		}
+		updateListContents();
+	}
+
+	@Override
+	protected void deleteItemAtIndex(int index)
+			throws IllegalArgumentException, NoSuchMethodException,
+			IllegalAccessException, InvocationTargetException,
+			ClassNotFoundException {
+		getListHandler();
+		synchronized (lh) {
+			getUnderlyingList().remove(index);
+			lh.remove(index);
+		}
+		updateListContents();
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	protected void itemMoved(int fromIndex, int toIndex)
+			throws IllegalArgumentException, NoSuchMethodException,
+			IllegalAccessException, InvocationTargetException,
+			ClassNotFoundException {
+		getListHandler();
+		synchronized (lh) {
+			Object toMove = getUnderlyingList().remove(fromIndex);
+			ListHandler.ListItem wrapperToMove = lh.remove(fromIndex);
+			getUnderlyingList().add(toIndex, toMove);
+			lh.add(toIndex, wrapperToMove);
+		}
+		updateListContents();
+	}
+
+	@Override
+	protected List<JComponent> getListComponents() throws NoSuchMethodException {
+		getListHandler();
+		List<JComponent> result = new ArrayList<JComponent>();
+		for (ListHandler.ListItem item : lh) {
+			JPanel listItem = new JPanel();
+			listItem.setOpaque(false);
+			listItem.setLayout(new BoxLayout(listItem, BoxLayout.PAGE_AXIS));
+			Class<?> itemClass = item.getValue().getClass();
+			if (itemClass.isEnum()) {
+				result.add(new BeanEnumComboBox(item, "value", false,
+						new Properties()));
+			} else {
+				result.add(new BeanTextField(item, "value", false,
+						new Properties()));
+			}
+		}
+		return result;
+	}
+
+	@SuppressWarnings("unchecked")
+	private ListHandler getListHandler() {
+		if (this.lh == null) {
+			lh = new ListHandler(getUnderlyingList());
+		}
+		return lh;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/package.html
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/package.html b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/package.html
new file mode 100644
index 0000000..7ee4684
--- /dev/null
+++ b/taverna-uibuilder/src/main/java/net/sf/taverna/t2/lang/uibuilder/package.html
@@ -0,0 +1,4 @@
+<body>
+Swing components to control and render the PluginManager and associated
+bean classes, most obviously PluginDescription
+</body>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/delete.png
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/delete.png b/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/delete.png
new file mode 100644
index 0000000..4ad6a58
Binary files /dev/null and b/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/delete.png differ

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/down.png
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/down.png b/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/down.png
new file mode 100644
index 0000000..c7b2f03
Binary files /dev/null and b/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/down.png differ

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/new.png
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/new.png b/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/new.png
new file mode 100644
index 0000000..7c437cf
Binary files /dev/null and b/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/new.png differ

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/up.png
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/up.png b/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/up.png
new file mode 100644
index 0000000..bace5c1
Binary files /dev/null and b/taverna-uibuilder/src/main/resources/net/sf/taverna/t2/lang/uibuilder/up.png differ

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/Application.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/Application.java b/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/Application.java
new file mode 100644
index 0000000..3f90241
--- /dev/null
+++ b/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/Application.java
@@ -0,0 +1,48 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.Color;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
+
+import net.sf.taverna.t2.lang.uibuilder.UIBuilder;
+
+/**
+ * Torture test for the UIBuilder, run this as an application
+ * 
+ * @author Tom Oinn
+ * 
+ */
+public class Application {
+
+	private static final String NUMBUS = "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel";
+
+	public static void main(String[] args) throws ClassNotFoundException,
+			InstantiationException, IllegalAccessException,
+			UnsupportedLookAndFeelException {
+		try {
+			UIManager.setLookAndFeel(NUMBUS);
+		} catch (ClassNotFoundException ex) {
+			// ignore
+		}
+		Object bean = new TopLevelBean();
+		JFrame win = new JFrame();
+		JPanel contents = UIBuilder.buildEditor(bean, new String[] {
+				"boundbean", "boundbean.string:type=textarea", "boundbean.url",
+				"boundbean.uri",
+				"boundbean.string:type=textarea", "boundbean.url",
+				"enumeratedfield", "nest", "nest.list", "nest.list.list1",
+				"nest.list.list2", "nest.list.list2.string",
+				"nest.list.list2.url" });
+		contents.setBackground(new Color(240, 230, 200));
+		win.setContentPane(new JScrollPane(contents));
+		win.setTitle("Bean test");
+		win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+		win.pack();
+		win.setVisible(true);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/Application2.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/Application2.java b/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/Application2.java
new file mode 100644
index 0000000..bf689c9
--- /dev/null
+++ b/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/Application2.java
@@ -0,0 +1,44 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.awt.Color;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
+
+import net.sf.taverna.t2.lang.uibuilder.UIBuilder;
+
+/**
+ * Torture test for the UIBuilder, run this as an application
+ * 
+ * @author Tom Oinn
+ * 
+ */
+public class Application2 {
+
+	private static final String NUMBUS = "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel";
+
+	public static void main(String[] args) throws ClassNotFoundException,
+			InstantiationException, IllegalAccessException,
+			UnsupportedLookAndFeelException {
+		try {
+			UIManager.setLookAndFeel(NUMBUS);
+		} catch (ClassNotFoundException ex) {
+			// ignore
+		}
+		Object bean = new PrimitiveTypeBean();
+		JFrame win = new JFrame();
+		JPanel contents = UIBuilder.buildEditor(bean, new String[] {
+				"intvalue", "shortValue", "longValue", "doubleValue",
+				"booleanValue", "byteValue", "floatValue", "charValue" });
+		contents.setBackground(new Color(240, 230, 200));
+		win.setContentPane(new JScrollPane(contents));
+		win.setTitle("Primitive type test");
+		win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+		win.pack();
+		win.setVisible(true);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithBoundProps.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithBoundProps.java b/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithBoundProps.java
new file mode 100644
index 0000000..3f91ea2
--- /dev/null
+++ b/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithBoundProps.java
@@ -0,0 +1,74 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+
+/**
+ * Example bean with string and URL properties, both of which fire property
+ * change events
+ * 
+ * @author Tom Oinn
+ * 
+ */
+public class BeanWithBoundProps {
+
+	private String string = "Default value";
+	private URL url;
+	private PropertyChangeSupport pcs;
+	private URI uri;
+	
+
+	public BeanWithBoundProps() {
+		try {
+			this.url = new URL("http://some.default.url");
+			this.pcs = new PropertyChangeSupport(this);
+			this.uri = URI.create("http://google.com/"); 
+		} catch (MalformedURLException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+	}
+
+	public void setUrl(URL url) {
+		URL old = this.url;
+		this.url = url;
+		this.pcs.firePropertyChange("url", old, url);
+	}
+
+	public URL getUrl() {
+		return url;
+	}
+	
+	
+
+	public void setString(String string) {
+		String old = this.string;
+		this.string = string;
+		this.pcs.firePropertyChange("string", old, string);
+	}
+
+	public String getString() {
+		return string;
+	}
+
+	public void addPropertyChangeListener(String propertyName,
+			PropertyChangeListener l) {
+		pcs.addPropertyChangeListener(propertyName, l);
+	}
+
+	public void removePropertyChangeListener(PropertyChangeListener l) {
+		pcs.removePropertyChangeListener(l);
+	}
+
+	public void setUri(URI uri) {
+		this.uri = uri;
+	}
+
+	public URI getUri() {
+		return uri;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithListProperties.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithListProperties.java b/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithListProperties.java
new file mode 100644
index 0000000..90d7c6a
--- /dev/null
+++ b/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithListProperties.java
@@ -0,0 +1,35 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Sample bean with a couple of list properties
+ * 
+ * @author Tom Oinn
+ * 
+ */
+public class BeanWithListProperties {
+
+	private List<String> list1;
+	private List<BeanWithBoundProps> list2;
+
+	public BeanWithListProperties() {
+		this.list1 = new ArrayList<String>();
+		this.list2 = new ArrayList<BeanWithBoundProps>();
+		list1.add("A list item");
+		list1.add("Another item");
+		for (int i = 0; i < 10; i++) {
+			list2.add(new BeanWithBoundProps());
+		}
+	}
+
+	public List<String> getList1() {
+		return this.list1;
+	}
+
+	public List<BeanWithBoundProps> getList2() {
+		return this.list2;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/fb641cfc/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithNestedList.java
----------------------------------------------------------------------
diff --git a/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithNestedList.java b/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithNestedList.java
new file mode 100644
index 0000000..231f068
--- /dev/null
+++ b/taverna-uibuilder/src/test/java/net/sf/taverna/t2/lang/uibuilder/BeanWithNestedList.java
@@ -0,0 +1,28 @@
+package net.sf.taverna.t2.lang.uibuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Simple bean with a list of the BeanWithListProperties to check nested list
+ * management
+ * 
+ * @author Tom Oinn
+ * 
+ */
+public class BeanWithNestedList {
+
+	private List<BeanWithListProperties> list;
+
+	public BeanWithNestedList() {
+		this.list = new ArrayList<BeanWithListProperties>();
+		for (int i = 0; i < 3; i++) {
+			list.add(new BeanWithListProperties());
+		}
+	}
+
+	public List<BeanWithListProperties> getList() {
+		return this.list;
+	}
+
+}