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 <[email protected]>
Authored: Fri Mar 6 22:28:51 2015 +0000
Committer: Stian Soiland-Reyes <[email protected]>
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;
+       }
+
+}

Reply via email to