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

[34/79] [partial] incubator-taverna-language git commit: Revert "temporarily empty repository"

http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/annotation/Revision.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/annotation/Revision.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/annotation/Revision.java
new file mode 100644
index 0000000..126cd85
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/annotation/Revision.java
@@ -0,0 +1,259 @@
+package org.apache.taverna.scufl2.api.annotation;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import java.net.URI;
+import java.util.Calendar;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import javax.annotation.processing.Processor;
+
+import org.apache.taverna.scufl2.api.common.AbstractCloneable;
+import org.apache.taverna.scufl2.api.common.URITools;
+import org.apache.taverna.scufl2.api.common.Visitor;
+import org.apache.taverna.scufl2.api.common.WorkflowBean;
+import org.apache.taverna.scufl2.api.configurations.Configuration;
+import org.apache.taverna.scufl2.api.container.WorkflowBundle;
+import org.apache.taverna.scufl2.api.core.Workflow;
+
+
+/**
+ * A record of a revision.
+ * <p>
+ * {@link Revisioned} workflow beans point to their latest Revision, which
+ * {@link #getIdentifier()} identifiers this version of the revisioned bean.
+ * <p>
+ * It is important that the identifier is world-wide unique, but also that it
+ * always identifies the same revision. It is not a requirement that the actual
+ * revision of the bean can be retrieved from the URI. {@link Revisioned}
+ * objects can mint UUID based URIs within namespaces of
+ * <code>http://ns.taverna.org.uk/</code> when using
+ * {@link Revisioned#newRevision()}.
+ * <p>
+ * The {@link #getPreviousRevision()} indicates the previous step in the chain
+ * of revisions which led to the current version. The revision only provides
+ * metadata about that older version, not the actual representation. (that is
+ * out of scope for scufl2, and should be handled by regular version control
+ * systems such as git).
+ * <p>
+ * A revision might note that compared to its previous revision, it has added (
+ * {@link #getAdditionOf()}), removed ( {@link #getRemovalOf()}) and/or modified
+ * ( {@link #getModificationsOf()}) resources. For instance, in a revision for a
+ * {@link Workflow}, the Revision might record the addition of a
+ * {@link Processor}; and in revision of a Profile, a modification of a
+ * {@link Configuration}.
+ * <p>
+ * Higher level, the revisions of a {@link WorkflowBundle} would record addition
+ * of a {@link Workflow} (which itself would have a separate Revision chain) -
+ * in this case {@link #getHadOriginalSources()} can indicate the workflow
+ * bundle that nested workflow and its configurations came from.
+ * <p>
+ * Revisions might be given a custom {@link #getChangeSpecificationType()} to
+ * indicate a particular kind of edit, for instance insertion of a nested
+ * workflow.
+ * 
+ * @author Stian Soiland-Reyes
+ */
+public class Revision extends AbstractCloneable implements WorkflowBean {
+	private Set<URI> additionOf = new LinkedHashSet<>();
+	private URI changeSpecificationType;
+	private Calendar generatedAtTime;
+	private Set<Revision> hadOriginalSources = new LinkedHashSet<>();
+	private URI identifier;
+	private Set<URI> modificationsOf = new LinkedHashSet<>();
+	private Revision previousRevision;
+	private Set<URI> removalOf = new LinkedHashSet<>();
+	private Set<URI> wasAttributedTo = new LinkedHashSet<>();
+
+	public Revision() {
+	}
+
+	public Revision(URI identifier, Revision previousRevision) {
+		this.identifier = identifier;
+		this.previousRevision = previousRevision;
+	}
+
+	public Set<URI> getAdditionOf() {
+		return additionOf;
+	}
+
+	public URI getChangeSpecificationType() {
+		return changeSpecificationType;
+	}
+
+	public Calendar getGeneratedAtTime() {
+		return generatedAtTime;
+	}
+
+	public Set<Revision> getHadOriginalSources() {
+		return hadOriginalSources;
+	}
+
+	public URI getIdentifier() {
+		return identifier;
+	}
+
+	public Set<URI> getModificationsOf() {
+		return modificationsOf;
+	}
+
+	public Revision getPreviousRevision() {
+		return previousRevision;
+	}
+
+	public Set<URI> getRemovalOf() {
+		return removalOf;
+	}
+
+	public Set<URI> getWasAttributedTo() {
+		return wasAttributedTo;
+	}
+
+	public void setAdditionOf(Set<URI> additionOf) {
+		this.additionOf.clear();
+		this.additionOf.addAll(additionOf);
+	}
+
+	public void setChangeSpecificationType(URI changeSpecificationType) {
+		this.changeSpecificationType = changeSpecificationType;
+	}
+
+	public void setGeneratedAtTime(Calendar generatedAtTime) {
+		this.generatedAtTime = generatedAtTime;
+	}
+
+	public void setHadOriginalSources(Set<Revision> hadOriginalSources) {
+		this.hadOriginalSources.clear();
+		this.hadOriginalSources.addAll(hadOriginalSources);
+	}
+
+	public void setIdentifier(URI identifier) {
+		this.identifier = identifier;
+	}
+
+	public void setModificationsOf(Set<URI> modificationsOf) {
+		this.modificationsOf.clear();
+		this.modificationsOf.addAll(modificationsOf);
+	}
+
+	public void setPreviousRevision(Revision previousRevision) {
+		this.previousRevision = previousRevision;
+	}
+
+	public void setRemovalOf(Set<URI> removalOf) {
+		this.removalOf.clear();
+		this.removalOf.addAll(removalOf);
+	}
+
+	public void setWasAttributedTo(Set<URI> wasAttributedTo) {
+		this.wasAttributedTo.clear();
+		this.wasAttributedTo.addAll(wasAttributedTo);
+	}
+
+	@Override
+	public boolean accept(Visitor visitor) {
+		return accept(visitor, new HashSet<Revision>());
+	}
+
+	protected boolean accept(Visitor visitor, HashSet<Revision> visited) {
+		if (!visited.add(this))
+			// Ignore this Revision, visitor has already seen it
+			return true;
+		boolean recurse = visitor.visitEnter(this);
+		if (recurse) {
+			if (getPreviousRevision() != null)
+				recurse = getPreviousRevision().accept(visitor, visited);
+			for (Revision rev : getHadOriginalSources()) {
+				if (!recurse)
+					break;
+				recurse = rev.accept(visitor, visited);
+			}
+		}
+		return visitor.visitLeave(this);
+	}
+
+	@Override
+	public String toString() {
+		return getClass().getSimpleName() + " " + getIdentifier();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (!(obj instanceof Revision))
+			return false;
+		Revision other = (Revision) obj;
+		if (getIdentifier() == null)
+			return obj == this;
+		return getIdentifier().equals(other.getIdentifier());
+	}
+
+	@Override
+	public int hashCode() {
+		if (getIdentifier() == null)
+			return 0x01234567;
+		return 0x01234567 ^ getIdentifier().hashCode();
+	}
+
+	@Override
+	protected void cloneInto(WorkflowBean clone, Cloning cloning) {
+		Revision cloneRevision = (Revision) clone;
+		cloneRevision.setAdditionOf(new LinkedHashSet<URI>(getAdditionOf()));
+		cloneRevision.setChangeSpecificationType(getChangeSpecificationType());
+		if (getGeneratedAtTime() != null)
+			cloneRevision.setGeneratedAtTime((Calendar) getGeneratedAtTime()
+					.clone());
+		for (Revision source : getHadOriginalSources())
+			cloneRevision.getHadOriginalSources().add(
+					cloning.cloneIfNotInCache(source));
+		cloneRevision.setIdentifier(getIdentifier());
+		cloneRevision.setModificationsOf(new LinkedHashSet<URI>(
+				getModificationsOf()));
+		cloneRevision.setPreviousRevision(cloning
+				.cloneIfNotInCache(getPreviousRevision()));
+		cloneRevision.setRemovalOf(new LinkedHashSet<URI>(getRemovalOf()));
+		cloneRevision.setWasAttributedTo(new LinkedHashSet<URI>(
+				getWasAttributedTo()));
+	}
+
+	// Derived operations
+
+	/**
+	 * Get the URI of this revision.
+	 * 
+	 * @return The absolute URI.
+	 * @see URITools#uriForBean(WorkflowBean)
+	 */
+	public URI getURI() {
+		return getUriTools().uriForBean(this);
+	}
+
+	/**
+	 * Get the URI of this revision relative to another workflow element.
+	 * 
+	 * @return The relative URI.
+	 * @see URITools#relativeUriForBean(WorkflowBean,WorflowBean)
+	 */
+	public URI getRelativeURI(WorkflowBean relativeTo) {
+		return getUriTools().relativeUriForBean(this, relativeTo);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/annotation/Revisioned.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/annotation/Revisioned.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/annotation/Revisioned.java
new file mode 100644
index 0000000..069379e
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/annotation/Revisioned.java
@@ -0,0 +1,120 @@
+package org.apache.taverna.scufl2.api.annotation;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import java.net.URI;
+import java.util.GregorianCalendar;
+
+import org.apache.taverna.scufl2.api.common.WorkflowBean;
+
+
+/**
+ * A WorkflowBean that is revisioned.
+ * <p>
+ * Revisions are expressed as a chain of {@link Revision}s linking to the
+ * {@link Revision#getPreviousRevision()}s. The Revision metadata also may
+ * include when and who did the revision.
+ * 
+ * @author Stian Soiland-Reyes
+ */
+public interface Revisioned extends WorkflowBean {
+	/**
+	 * Get the current Revision metadata.
+	 * <p>
+	 * The {@link Revision} typically contains information about when it was
+	 * made, and links to the previous revision chain.
+	 * 
+	 * @return
+	 */
+	Revision getCurrentRevision();
+
+	/**
+	 * Set the current Revision.
+	 * <p>
+	 * To preserve the existing revision chain, the new revision should point to
+	 * the current revision using {@link Revision#setPreviousRevision(Revision)}
+	 * 
+	 * @param currentRevision
+	 *            The {@link Revision} to be set
+	 */
+	void setCurrentRevision(Revision currentRevision);
+
+	/**
+	 * Make a new Revision to mark structural changes to this workflow bean.
+	 * <p>
+	 * The identifier of the new {@link #getCurrentRevision()} will also be
+	 * identifying the Revisioned workflow bean and be returned from
+	 * {@link #getIdentifier()}.
+	 * <p>
+	 * The new revision will include the previous Revision as
+	 * {@link Revision#getPreviousRevision()} and
+	 * {@link Revision#getGeneratedAtTime()} on the new revision will match the
+	 * current {@link GregorianCalendar} by default.
+	 * 
+	 * @return The new {@link #getCurrentRevision()}, for setting any further
+	 *         details.
+	 */
+	Revision newRevision();
+
+	/**
+	 * Make a new Revision to mark structural changes to this workflow bean with
+	 * the given identifier.
+	 * <p>
+	 * {@link #getIdentifier()} will match the new identifier. The new
+	 * {@link #getCurrentRevision()} will include the previous revision as
+	 * {@link Revision#getPreviousRevision()}.
+	 * <p>
+	 * Note, unlike the convenience method {@link #newRevision()} this method
+	 * will not update {@link Revision#getGeneratedAtTime()}.
+	 * 
+	 * @param revisionIdentifier
+	 *            The new workflow identifier
+	 * @return The new {@link #getCurrentRevision()}, for setting any further
+	 *         details.
+	 */
+	Revision newRevision(URI revisionIdentifier);
+
+	/**
+	 * Set the identifier.
+	 * <p>
+	 * This will delete any previous revisions in {@link #getCurrentRevision()}.
+	 * To avoid loosing history, you might instead want to use
+	 * {@link #newRevision(URI)}.
+	 * 
+	 * @see #getIdentifier()
+	 * @see #getCurrentRevision()
+	 * 
+	 * @param workflowIdentifier
+	 *            the identifier
+	 */
+	void setIdentifier(URI workflowIdentifier);
+
+	/**
+	 * Returns the identifier of this bean.
+	 * <p>
+	 * This identifier matches the {@link Revision#getIdentifier()} of
+	 * {@link #getCurrentRevision()}.
+	 * 
+	 * @see {@link #setIdentifier(URI)}
+	 * @return the identifier
+	 */
+	URI getIdentifier();
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/annotation/package-info.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/annotation/package-info.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/annotation/package-info.java
new file mode 100644
index 0000000..bf568ec
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/annotation/package-info.java
@@ -0,0 +1,27 @@
+/**
+ * Common annotations
+ * 
+ */
+package org.apache.taverna.scufl2.api.annotation;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+

http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/AbstractCloneable.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/AbstractCloneable.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/AbstractCloneable.java
new file mode 100644
index 0000000..c7f0326
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/AbstractCloneable.java
@@ -0,0 +1,173 @@
+package org.apache.taverna.scufl2.api.common;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+public abstract class AbstractCloneable implements WorkflowBean {
+	protected static class CopyVisitor implements Visitor {
+		private Cloning cloning;
+
+		public CopyVisitor(Cloning cloning) {
+			this.cloning = cloning;
+		}
+
+		@Override
+		public boolean visit(WorkflowBean node) {
+			cloneNode(node);
+			return true;
+		}
+
+		@Override
+		public boolean visitEnter(WorkflowBean node) {
+			cloneNode(node);
+			return true;
+		}
+
+		@Override
+		public boolean visitLeave(WorkflowBean node) {
+			return true;
+		}
+
+		@SuppressWarnings({ "rawtypes", "unchecked" })
+		public void cloneNode(WorkflowBean node) {
+			WorkflowBean clone = cloning.cloneIfNotInCache((WorkflowBean) node);
+			if (node instanceof Child && clone instanceof Child) {
+				Child child = (Child) node;
+				Child childClone = (Child) clone;
+				WorkflowBean oldParent = child.getParent();
+				// NOTE: oldParent==null is OK, as cloned is HashMap
+
+				/*
+				 * NOTE: We don't clone the parent! If it's not already cloned,
+				 * then it might be above our current visit tree and should not
+				 * be cloned (this is the case for the top level node). The
+				 * clone will then have the parent as null.
+				 */
+				WorkflowBean newParent = cloning.getCloned(oldParent);
+				childClone.setParent(newParent);
+			}
+		}
+	}
+
+	@Override
+	public AbstractCloneable clone() {
+		return cloneWorkflowBean(this);
+	}
+
+	public static <T extends WorkflowBean> T cloneWorkflowBean(T obj) {
+		Cloning cloning = new Cloning(obj);
+		CopyVisitor copyVisitor = new CopyVisitor(cloning);
+		obj.accept(copyVisitor);
+		return cloning.getCloned(obj);
+	}
+
+	public static class Cloning {
+		/**
+		 * Use identify map so we always make new copies of objects that just
+		 * may look alike, but are different, like empty lists
+		 */
+		private final Map<WorkflowBean, WorkflowBean> cloned = new IdentityHashMap<>();
+
+		/**
+		 * Construct a Cloning helper.
+		 * 
+		 * @param ancestor
+		 *            The highest WorkflowBean in the hierarchy to clone. Any
+		 *            beans that are 'below' this ancestor will be cloned.
+		 */
+		public Cloning(WorkflowBean ancestor) {
+			this.ancestor = ancestor;
+		}
+
+		final WorkflowBean ancestor;
+
+		@SuppressWarnings("unchecked")
+		public <T extends WorkflowBean> T cloneOrOriginal(T original) {
+			if (cloned.containsKey(original))
+				return (T) cloned.get(original);
+			return original;
+		}
+
+		@SuppressWarnings("unchecked")
+		public <T extends WorkflowBean> T cloneIfNotInCache(T original) {
+			if (original == null)
+				return null;
+			if (cloned.containsKey(original))
+				return (T) cloned.get(original);
+			T clone;
+			try {
+				clone = (T) original.getClass().newInstance();
+			} catch (InstantiationException e) {
+				// System.err.println("Can't do this one.. " + original);
+				return null;
+			} catch (IllegalAccessException e) {
+				throw new RuntimeException(e);
+			}
+			cloned.put(original, clone);
+
+			if (original instanceof AbstractCloneable) {
+				AbstractCloneable cloneable = (AbstractCloneable) original;
+				cloneable.cloneInto(clone, this);
+			}
+
+			// System.out.println("Cloned " + clone);
+			return clone;
+		}
+
+		@SuppressWarnings("unchecked")
+		public <T extends WorkflowBean> T getCloned(T originalBean) {
+			return (T) cloned.get(originalBean);
+		}
+
+		public <T extends WorkflowBean> void knownClone(T original, T clone) {
+			cloned.put(original, clone);
+		}
+	}
+
+	protected abstract void cloneInto(WorkflowBean clone, Cloning cloning);
+
+	private transient Scufl2Tools tools;
+	private transient URITools uriTools;
+
+	public Scufl2Tools getTools() {
+		if (tools == null && this instanceof Child) {
+			WorkflowBean parent = ((Child<?>) this).getParent();
+			if (parent instanceof AbstractCloneable)
+				tools = ((AbstractCloneable) parent).getTools();
+		}
+		if (tools == null)
+			tools = new Scufl2Tools();
+		return tools;
+	}
+
+	public URITools getUriTools() {
+		if (uriTools == null && this instanceof Child) {
+			WorkflowBean parent = ((Child<?>) this).getParent();
+			if (parent instanceof AbstractCloneable)
+				uriTools = ((AbstractCloneable) parent).getUriTools();
+		}
+		if (uriTools == null)
+			uriTools = new URITools();
+		return uriTools;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/AbstractNamed.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/AbstractNamed.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/AbstractNamed.java
new file mode 100644
index 0000000..121dadb
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/AbstractNamed.java
@@ -0,0 +1,223 @@
+package org.apache.taverna.scufl2.api.common;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.UUID;
+import java.util.regex.Matcher;
+
+import org.apache.taverna.scufl2.api.annotation.Annotation;
+
+
+/**
+ * Abstract implementation of a {@link Named} {@link WorkflowBean}.
+ * 
+ * @author Alan R Williams
+ * @author Stian Soiland-Reyes
+ */
+public abstract class AbstractNamed extends AbstractCloneable implements Named  {
+	private String name;
+
+	/**
+	 * Constructs a {@link Named} {@link WorkflowBean} with a random UUID as the name.
+	 */
+	public AbstractNamed() {
+		setName(UUID.randomUUID().toString());
+	}
+
+	/**
+	 * Constructs a {@link Named} {@link WorkflowBean} with the specified name.
+	 * 
+	 * @param name
+	 *            the name of the <code>Named</code> <code>WorkflowBean</code>. <strong>Must not</strong> be <code>null</code>
+	 *            or an empty String.
+	 */
+	public AbstractNamed(String name) {
+		setName(name);
+	}
+
+	@Override
+	public int compareTo(Object o) {
+		if (!(o instanceof AbstractNamed))
+			// Other comparables go first
+			return 1;
+		AbstractNamed other = (AbstractNamed) o;
+		if (other == this)
+			return 0;
+		/**
+		 * Disabled as this means the order changes depending on setParents being called or not;
+		 * could cause a DataLink to appear twice in workflow.getDataLinks(). 
+		 * 
+		 * 
+		if (this instanceof Child) {
+			if (!(other instanceof Child)) {
+				// He's orphan, he's sorted first
+				return 1;
+			}
+			WorkflowBean parent = ((Child<?>) this).getParent();
+			WorkflowBean otherParent = ((Child<?>) other).getParent();
+			if (parent instanceof Comparable && otherParent instanceof Comparable) {
+				int comparedParents = ((Comparable) parent).compareTo(otherParent);
+				if (comparedParents != 0) {
+					return comparedParents;
+				}
+			}
+		} else {
+			if (other instanceof Child) {
+				// We're orphan, we're first
+				return -1;
+			}
+		}
+		*/
+		if (getClass() != other.getClass()) {
+			int classCompare = getClass().getCanonicalName().compareTo(
+					other.getClass().getCanonicalName());
+			if (classCompare != 0)
+				// Allow having say InputPorts and OutputPorts in the same sorted list
+				return classCompare;
+		}
+		// We're the same class, let's compare the names
+		return getName().compareTo(other.getName());
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		AbstractNamed other = (AbstractNamed) obj;
+		if (!getName().equals(other.getName()))
+			return false;
+		if (this instanceof Child) {
+			WorkflowBean parent = ((Child<?>) this).getParent();
+			WorkflowBean otherParent = ((Child<?>) other).getParent();
+			if (parent != null)
+				return parent.equals(otherParent);
+			if (parent == null && otherParent != null)
+				return false;
+		}
+		if (this instanceof Typed) {
+			URI myId = ((Typed) this).getType();
+			URI otherId = ((Typed) obj).getType();
+			if (myId != null)
+				return myId.equals(otherId);
+			if (myId == null && otherId != null)
+				return false;
+		}
+		return true;
+	}
+
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	@SuppressWarnings({ "rawtypes" })
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + (name == null ? 0 : name.hashCode());
+
+		if (this instanceof Child) {
+			WorkflowBean parent = ((Child) this).getParent();
+			if (parent != null)
+				result = prime * result + parent.hashCode();
+		}
+		return result;
+	}
+
+	@Override
+	@SuppressWarnings({ "unchecked", "rawtypes" })
+	public void setName(String name) {
+		if (name == null)
+			throw new NullPointerException("Name can't be null");
+		Matcher invalidMatcher = INVALID_NAME.matcher(name);
+		if (invalidMatcher.find())
+		    // http://dev.mygrid.org.uk/issues/browse/SCUFL2-87
+		    // TODO: Any other characters that must be disallowed?
+			throw new IllegalArgumentException("Name invalid in position "
+					+ invalidMatcher.start() + ": '" + name + "'");
+
+		if (this instanceof Child) {
+			Child child = (Child) this;
+			WorkflowBean parent = child.getParent();
+			if (parent != null) {
+				child.setParent(null);
+				this.name = name;
+				// Might overwrite other Named object with same name
+				child.setParent(parent);
+			}
+		}
+		this.name = name;
+	}
+
+	@Override
+	public String toString() {
+		return getClass().getSimpleName() + " \"" + getName() + '"';
+	}
+	
+	@Override
+	protected void cloneInto(WorkflowBean clone, Cloning cloning) {
+		AbstractNamed namedClone = (AbstractNamed)clone;
+		namedClone.setName(getName());
+	}
+
+	// Derived operations
+
+	/**
+	 * Get all the annotations that pertain to this workflow element.
+	 * 
+	 * @return The collection of annotations.
+	 * @see Scufl2Tools#annotationsFor(Child)
+	 */
+	public Collection<Annotation> getAnnotations() {
+		if (this instanceof Child)
+			return getTools().annotationsFor((Child<?>) this);
+		throw new UnsupportedOperationException(
+				"operation needs to be overridden for root elements");
+	}
+
+	/**
+	 * Get the URI of this workflow element.
+	 * 
+	 * @return The absolute URI.
+	 * @see URITools#uriForBean(WorkflowBean)
+	 */
+	public URI getURI() {
+		return getUriTools().uriForBean(this);
+	}
+
+	/**
+	 * Get the URI of this workflow element relative to another workflow
+	 * element.
+	 * 
+	 * @return The relative URI.
+	 * @see URITools#relativeUriForBean(WorkflowBean,WorflowBean)
+	 */
+	public URI getRelativeURI(WorkflowBean relativeTo) {
+		return getUriTools().relativeUriForBean(this, relativeTo);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/AbstractRevisioned.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/AbstractRevisioned.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/AbstractRevisioned.java
new file mode 100644
index 0000000..42e094c
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/AbstractRevisioned.java
@@ -0,0 +1,114 @@
+package org.apache.taverna.scufl2.api.common;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import java.net.URI;
+import java.util.GregorianCalendar;
+import java.util.UUID;
+
+import org.apache.taverna.scufl2.api.annotation.Revision;
+import org.apache.taverna.scufl2.api.annotation.Revisioned;
+
+
+public abstract class AbstractRevisioned extends AbstractNamed implements
+		Revisioned {
+	private Revision currentRevision;
+
+	protected URI generateNewIdentifier() {
+		return getIdentifierRoot().resolve(UUID.randomUUID().toString() + "/");
+	}
+
+	protected abstract URI getIdentifierRoot();
+
+	public AbstractRevisioned() {
+		newRevision();
+		String id = getIdentifierRoot().relativize(getIdentifier())
+				.toASCIIString().replace("/", "");
+		setName(id);
+	}
+
+	public AbstractRevisioned(String name) {
+		super(name);
+		newRevision();
+	}
+
+	@Override
+	public void setCurrentRevision(Revision currentRevision) {
+		this.currentRevision = currentRevision;
+		if (currentRevision == null)
+			newRevision();
+	}
+
+	@Override
+	public Revision newRevision() {
+		return newRevision(null);
+	}
+
+	@Override
+	public Revision newRevision(URI revisionIdentifier) {
+		GregorianCalendar created = null;
+		if (revisionIdentifier == null) {
+			revisionIdentifier = generateNewIdentifier();
+			created = new GregorianCalendar();
+		}
+		Revision newRevision = new Revision(revisionIdentifier,
+				getCurrentRevision());
+		newRevision.setGeneratedAtTime(created);
+		setCurrentRevision(newRevision);
+		return newRevision;
+	}
+
+	@Override
+	public Revision getCurrentRevision() {
+		return currentRevision;
+	}
+
+	/**
+	 * Returns the identifier.
+	 * <p>
+	 * The the default identifier is based on #getIdentifierRoot() plus a random
+	 * UUID.
+	 * 
+	 * @see {@link #setIdentifier(URI)}
+	 * @return the identifier
+	 */
+	@Override
+	public URI getIdentifier() {
+		if (getCurrentRevision() == null)
+			return null;
+		return getCurrentRevision().getIdentifier();
+	}
+
+	/**
+	 * Set the identifier.
+	 * <p>
+	 * This will delete any previous revisions in {@link #getCurrentRevision()}
+	 * 
+	 * @see #getIdentifier()
+	 * @see #getCurrentRevision()
+	 * @param identifier
+	 *            the identifier
+	 */
+	@Override
+	public void setIdentifier(URI identifier) {
+		setCurrentRevision(new Revision(identifier, null));
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Child.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Child.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Child.java
new file mode 100644
index 0000000..b5d9893
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Child.java
@@ -0,0 +1,58 @@
+package org.apache.taverna.scufl2.api.common;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+/**
+ * A {@link WorkflowBean} that is the child of another <code>WorkflowBean</code>
+ * .
+ * @author Alan R Williams
+ * @author Stian Soiland-Reyes
+ * 
+ * @param <T>
+ *            the type of <code>WorkflowBean</code> that this is a child of
+ */
+public interface Child<T extends WorkflowBean> extends WorkflowBean {
+	/**
+	 * @return the parent of this workflow bean, or <code>null</code> if it is
+	 *         orphan
+	 */
+	T getParent();
+
+	/**
+	 * Sets the parent of this workflow bean.
+	 * <p>
+	 * Setting the parent would normally also add the object to the relevant
+	 * collection in the parent if it does not already exist there.
+	 * <p>
+	 * If the child has an existing, object-identity different parent, the child
+	 * will first be removed from the parent collection if it exists there.
+	 * <p>
+	 * <strong>Note:</strong>If the child is {@link Named} the parent collection
+	 * will be a {@link NamedSet}. This implicit insertion would overwrite any
+	 * conflicting sibling with the same {@link Named#getName()} - to avoid
+	 * this, add the child to the parent collection by using
+	 * {@link NamedSet#addWithUniqueName(Named)} before setting the parent.
+	 * 
+	 * @param parent
+	 *            the parent of this workflow bean
+	 */
+	void setParent(T parent);
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Configurable.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Configurable.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Configurable.java
new file mode 100644
index 0000000..b90e502
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Configurable.java
@@ -0,0 +1,36 @@
+package org.apache.taverna.scufl2.api.common;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import org.apache.taverna.scufl2.api.configurations.Configuration;
+
+/**
+ * {@link WorkflowBean WorkflowBeans} that can have a
+ * {@link org.apache.taverna.scufl2.api.configurations.Configuration Configuration}.
+ * <p>
+ * Configurables are {@link Typed}, but note that this type is different from
+ * the type of the {@link Configuration}.
+ * 
+ * @author Alan R Williams
+ * @author Stian Soiland-Reyes
+ */
+public interface Configurable extends Typed {
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Named.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Named.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Named.java
new file mode 100644
index 0000000..0518282
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Named.java
@@ -0,0 +1,55 @@
+package org.apache.taverna.scufl2.api.common;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import java.util.regex.Pattern;
+
+/**
+ * A named {@link WorkflowBean}.
+ * 
+ * @author Alan R Williams
+ */
+@SuppressWarnings("rawtypes")
+public interface Named extends WorkflowBean, Comparable {
+	/**
+	 * Name must not match this regular expression, e.g. must not include: slash
+	 * (/), colon (:), ASCII control characters
+	 */
+	Pattern INVALID_NAME = Pattern.compile("^$|[/:\\x7f\\x00-\\x1f]");
+
+	/**
+	 * Returns the name of the {@link WorkflowBean}.
+	 * 
+	 * @return the name of the <code>WorkflowBean</code>
+	 */
+	String getName();
+
+	/**
+	 * Sets the name of the {@link WorkflowBean}.
+	 * 
+	 * The name <strong>must not</strong> be <code>null</code>, not be an empty
+	 * String, and must not match the {@link #INVALID_NAME} regular expression.
+	 * 
+	 * @param name
+	 *            the name of the <code>WorkflowBean</code>
+	 */
+	void setName(String name);
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/NamedSet.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/NamedSet.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/NamedSet.java
new file mode 100644
index 0000000..f449d24
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/NamedSet.java
@@ -0,0 +1,290 @@
+package org.apache.taverna.scufl2.api.common;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+
+/**
+ * A {@link java.util.Set} of {@link Named} {@link WorkflowBean}.
+ * <p>
+ * This set will guarantee to never contain more than one {@link Named} object per
+ * {@link Named#getName()}.
+ * <p>
+ * It is also possible to retrieve values by name using {@link #getByName(String)}, or remove using
+ * {@link #removeByName(String)}. The names can also be found in {@link #getNames()} and
+ * {@link #nameIterator()}
+ * <p>
+ * Internally this set is backed by a {@link TreeMap}.
+ * 
+ * @author Stian Soiland-Reyes
+ * 
+ * @param <T>
+ *            subclass of {@link Named} to keep in this set.
+ */
+public class NamedSet<T extends Named> extends AbstractSet<T> implements SortedSet<T> {
+	protected transient SortedMap<String, T> namedMap;
+
+	/**
+	 * Constructs an empty <code>NamedSet</code>.
+	 */
+	public NamedSet() {
+		namedMap = new TreeMap<>();
+	}
+
+	/**
+	 * Constructs a <code>NamedSet</code> containing all the elements of the given collection.
+	 * <p>
+	 * If the collection contains several {@link Named} elements with the same name, only the last
+	 * of those elements will be in the new <code>NamedSet</code>.
+	 * 
+	 * @param collection
+	 *            the collection whose elements are to be added to the set
+	 */
+	public NamedSet(Collection<? extends T> collection) {
+		namedMap = new TreeMap<>();
+		addAll(collection);
+	}
+
+	/**
+	 * Adds the {@link Named} {@link WorkflowBean} and returns <code>true</code> iff the set did not
+	 * already contain an element with the same name.
+	 * 
+	 * The element <strong>must not</strong> be <code>null</code>
+	 * <p>
+	 * If a different (object identity) element with the same name already exists in the set, the
+	 * element will be replaced the new element and <code>false</code> will be returned.
+	 * <p>
+	 * Inserting the same object instance a second time will have no effect.
+	 * 
+	 * @return <code>true</code> iff the set did not already contain an element with the same name
+	 */
+	@Override
+	public boolean add(T named) {
+		return namedMap.put(named.getName(), named) == null;
+	}
+
+	/**
+	 * Adds the {@link Named} {@link WorkflowBean} with a unique name if the name already exist.
+	 * <p>
+	 * Similar to {@link #add(Named)}, but if a (object identity) different element with the same
+	 * name already exists in the set, the element to be inserted will be given a unique name before
+	 * inserting.
+	 * <p>
+	 * This means that inserting the same object instance a second time will have no effect, as with
+	 * {@link #add(Named)}.
+	 * <p>
+	 * The unique name is generated by appending <code>_x</code> to the existing name, where x is a
+	 * number from 2 or higher. An existing numeric prefix will first be removed.
+	 * <p>
+	 * For example, if the set already contains elements named "fish", "fish_2" and "fish_5", but
+	 * you try to insert a different element which is named "fish" or "fish_5", it will be renamed
+	 * to "fish_3" before inserting.
+	 * <p>
+	 * <strong>Note:</strong> If the element is a {@link Child} instance, and it's
+	 * {@link Child#getParent()} is not <code>null</code> (or this set) the renaming could
+	 * potentially cause a conflict in the old parent set. (In the example above, if the element's
+	 * old parent has a child "fish_3" it will be overwritten by this instance).
+	 * 
+	 * @param element
+	 *            the <code>Named WorkflowBean</code> to add
+	 * @return the final {@link T#getName()} after insertion
+	 */
+	public String addWithUniqueName(T element) {
+		String name = element.getName();
+		T existing = getByName(name);
+		if (element == existing)
+			return name;
+
+		// Remove any existing number suffix
+		String nameTemplate = name.replaceAll("_\\d+$", "_");
+
+		long i = 1;
+		while (getNames().contains(name))
+			name = nameTemplate + i++;
+
+		element.setName(name);
+		add(element);
+		return name;
+	}
+
+	@Override
+	public void clear() {
+		namedMap.clear();
+	}
+
+	@SuppressWarnings({ "unchecked", "rawtypes" })
+	@Override
+	public NamedSet<T> clone() {
+		NamedSet<T> copy;
+		try {
+			copy = (NamedSet<T>) super.clone();
+		} catch (CloneNotSupportedException e) {
+			throw new IllegalStateException(e);
+		}
+		if (!(this.namedMap instanceof TreeMap))
+			throw new IllegalStateException("Can't clone submap");
+		copy.namedMap = (SortedMap<String, T>) ((TreeMap) this.namedMap).clone();
+		return copy;
+	}
+
+	@Override
+	public Comparator<? super T> comparator() {
+		return null;
+	}
+
+	/**
+	 * Return <code>true</code> if the <code>NamedSet</code> contains the given object, as compared
+	 * using its {@link Object#equals(Object)} method.
+	 * <p>
+	 * Note that if a different object with the same name exists, this method will return
+	 * <code>false</code>. To check for existence of a name, use {@link #containsName(String)}.
+	 * 
+	 * @see Collection#contains(Object)
+	 * @param o
+	 * @return
+	 */
+	@Override
+	public boolean contains(Object o) {
+		if (!(o instanceof Named))
+			return false;
+		Named named = (Named) o;
+		return named.equals(namedMap.get(named.getName()));
+	}
+
+	/**
+	 * Return <code>true</code> if the <code>NamedSet</code> contains an element with the given
+	 * name.
+	 * 
+	 * @param name
+	 *            the name of object
+	 * @return <code>true</code> if an element with given name is in set
+	 */
+	public boolean containsName(String name) {
+		return namedMap.containsKey(name);
+	}
+
+	@Override
+	public T first() {
+		return namedMap.get(namedMap.firstKey());
+	}
+
+	/**
+	 * Return the element with the given name from the set.
+	 * 
+	 * Returns <code>null</code> if the set does not contain an element with the given name.
+	 * 
+	 * @param name
+	 *            the name of the element to return
+	 * @return the element with the given name from the set
+	 */
+	public T getByName(String name) {
+		return namedMap.get(name);
+	}
+
+	/**
+	 * Returns a set of the names of the elements in this set.
+	 * 
+	 * @return a set of the names of the elements in this set
+	 */
+	public Set<String> getNames() {
+		return namedMap.keySet();
+	}
+
+	@Override
+	public SortedSet<T> headSet(T toElement) {
+		// FIXME: Return a view instead of a copy
+		NamedSet<T> headSet = new NamedSet<>();
+		headSet.namedMap = namedMap.headMap(toElement.getName());
+		return headSet;
+	}
+
+	@Override
+	public boolean isEmpty() {
+		return namedMap.isEmpty();
+	}
+
+	@Override
+	public Iterator<T> iterator() {
+		return namedMap.values().iterator();
+	}
+
+	@Override
+	public T last() {
+		return namedMap.get(namedMap.lastKey());
+	}
+
+	public Iterator<String> nameIterator() {
+		return namedMap.keySet().iterator();
+	}
+
+	@Override
+	public boolean remove(Object o) {
+		if (!(o instanceof Named))
+			return false;
+		Named named = (Named) o;
+		String name = named.getName();
+		T exists = namedMap.get(name);
+		if (named.equals(exists))
+			return namedMap.remove(named.getName()) != null;
+		return false;
+	}
+
+	/**
+	 * Removes the element with the given name.
+	 * 
+	 * Returns the {@link Named} element that was removed or <code>null</code> if the set did not
+	 * contain an element with the given name.
+	 * 
+	 * @param name
+	 *            the name of the element to remove
+	 * @return the {@link Named} element that was removed or <code>null</code> if the set did not
+	 *         contain an element with the given name
+	 */
+	public T removeByName(String name) {
+		return namedMap.remove(name);
+	}
+
+	@Override
+	public int size() {
+		return namedMap.size();
+	}
+
+	@Override
+	public SortedSet<T> subSet(T fromElement, T toElement) {
+		NamedSet<T> headSet = new NamedSet<>();
+		headSet.namedMap = namedMap.subMap(fromElement.getName(), toElement.getName());
+		return headSet;
+	}
+
+	@Override
+	public SortedSet<T> tailSet(T fromElement) {
+		NamedSet<T> headSet = new NamedSet<>();
+		headSet.namedMap = namedMap.tailMap(fromElement.getName());
+		return headSet;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Ported.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Ported.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Ported.java
new file mode 100644
index 0000000..83c4168
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Ported.java
@@ -0,0 +1,46 @@
+package org.apache.taverna.scufl2.api.common;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import org.apache.taverna.scufl2.api.port.InputPort;
+import org.apache.taverna.scufl2.api.port.OutputPort;
+
+/**
+ * A {@link WorkflowBean} that has
+ * {@link org.apache.taverna.scufl2.api.portInputPort InputPorts} and
+ * {@link org.apache.taverna.scufl2.api.portOutputPort OutputPorts}.
+ */
+public interface Ported extends WorkflowBean {
+	/**
+	 * Returns the {@link org.apache.taverna.scufl2.api.port.InputPort InputPorts}.
+	 * 
+	 * @return the input ports
+	 */
+	NamedSet<? extends InputPort> getInputPorts();
+
+	/**
+	 * Returns the {@link org.apache.taverna.scufl2.api.port.OutputPort OutputPorts}
+	 * .
+	 * 
+	 * @return the output ports
+	 */
+	NamedSet<? extends OutputPort> getOutputPorts();
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Root.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Root.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Root.java
new file mode 100644
index 0000000..bc24fab
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Root.java
@@ -0,0 +1,29 @@
+package org.apache.taverna.scufl2.api.common;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import java.net.URI;
+
+public interface Root extends WorkflowBean {
+	URI getGlobalBaseURI();
+
+	void setGlobalBaseURI(URI globalBaseURI);
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Scufl2Tools.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Scufl2Tools.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Scufl2Tools.java
new file mode 100644
index 0000000..3782580
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Scufl2Tools.java
@@ -0,0 +1,866 @@
+package org.apache.taverna.scufl2.api.common;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.xml.bind.PropertyException;
+
+import org.apache.taverna.scufl2.api.activity.Activity;
+import org.apache.taverna.scufl2.api.annotation.Annotation;
+import org.apache.taverna.scufl2.api.common.Visitor.VisitorWithPath;
+import org.apache.taverna.scufl2.api.configurations.Configuration;
+import org.apache.taverna.scufl2.api.container.WorkflowBundle;
+import org.apache.taverna.scufl2.api.core.BlockingControlLink;
+import org.apache.taverna.scufl2.api.core.ControlLink;
+import org.apache.taverna.scufl2.api.core.DataLink;
+import org.apache.taverna.scufl2.api.core.Processor;
+import org.apache.taverna.scufl2.api.core.Workflow;
+import org.apache.taverna.scufl2.api.port.ActivityPort;
+import org.apache.taverna.scufl2.api.port.InputActivityPort;
+import org.apache.taverna.scufl2.api.port.InputPort;
+import org.apache.taverna.scufl2.api.port.InputProcessorPort;
+import org.apache.taverna.scufl2.api.port.OutputActivityPort;
+import org.apache.taverna.scufl2.api.port.OutputPort;
+import org.apache.taverna.scufl2.api.port.OutputProcessorPort;
+import org.apache.taverna.scufl2.api.port.Port;
+import org.apache.taverna.scufl2.api.port.ProcessorPort;
+import org.apache.taverna.scufl2.api.port.ReceiverPort;
+import org.apache.taverna.scufl2.api.port.SenderPort;
+import org.apache.taverna.scufl2.api.profiles.ProcessorBinding;
+import org.apache.taverna.scufl2.api.profiles.ProcessorInputPortBinding;
+import org.apache.taverna.scufl2.api.profiles.ProcessorOutputPortBinding;
+import org.apache.taverna.scufl2.api.profiles.ProcessorPortBinding;
+import org.apache.taverna.scufl2.api.profiles.Profile;
+
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Utility methods for dealing with SCUFL2 models
+ * 
+ * @author Stian Soiland-Reyes
+ */
+public class Scufl2Tools {
+	private static final String CONSTANT_STRING = "string";
+	private static final String CONSTANT_VALUE_PORT = "value";
+	public static URI PORT_DEFINITION = URI
+			.create("http://ns.taverna.org.uk/2010/scufl2#portDefinition");
+	private static URITools uriTools = new URITools();
+	public static URI NESTED_WORKFLOW = URI
+			.create("http://ns.taverna.org.uk/2010/activity/nested-workflow");
+
+	/**
+	 * Compare {@link ProcessorBinding}s by their
+	 * {@link ProcessorBinding#getActivityPosition()}.
+	 * <p>
+	 * <b>Note:</b> this comparator imposes orderings that are inconsistent with
+	 * equals.
+	 * 
+	 * @author Stian Soiland-Reyes
+	 */
+	public static class BindingComparator implements
+			Comparator<ProcessorBinding> {
+		@Override
+		public int compare(ProcessorBinding o1, ProcessorBinding o2) {
+			return o1.getActivityPosition() - o2.getActivityPosition();
+		}
+	}
+
+	public List<Annotation> annotationsFor(Child<?> bean) {
+		WorkflowBundle bundle = findParent(WorkflowBundle.class, bean);
+		return annotationsFor(bean, bundle);
+	}
+
+	public List<Annotation> annotationsFor(WorkflowBean bean,
+			WorkflowBundle bundle) {
+		ArrayList<Annotation> annotations = new ArrayList<>();
+		if (bundle == null)
+			return annotations;
+		for (Annotation ann : bundle.getAnnotations())
+			if (ann.getTarget().equals(bean))
+				annotations.add(ann);
+		return annotations;
+	}
+
+	/**
+	 * Returns the {@link Configuration} for a {@link Configurable} in the given
+	 * {@link Profile}.
+	 * 
+	 * @param configurable
+	 *            the <code>Configurable</code> to find a
+	 *            <code>Configuration</code> for
+	 * @param profile
+	 *            the <code>Profile</code> to look for the
+	 *            <code>Configuration</code> in
+	 * @return the <code>Configuration</code> for a <code>Configurable</code> in
+	 *         the given <code>Profile</code>
+	 */
+	public Configuration configurationFor(Configurable configurable,
+			Profile profile) {
+		List<Configuration> configurations = configurationsFor(configurable,
+				profile);
+		if (configurations.isEmpty())
+			throw new IndexOutOfBoundsException(
+					"Could not find configuration for " + configurable);
+		if (configurations.size() > 1)
+			throw new IllegalStateException("More than one configuration for "
+					+ configurable);
+		return configurations.get(0);
+	}
+
+	public Configuration configurationForActivityBoundToProcessor(
+			Processor processor, Profile profile) {
+		ProcessorBinding binding = processorBindingForProcessor(processor,
+				profile);
+		return configurationFor(binding.getBoundActivity(), profile);
+	}
+
+	/**
+	 * Returns the list of {@link Configuration Configurations} for a
+	 * {@link Configurable} in the given {@link Profile}.
+	 * 
+	 * @param configurable
+	 *            the <code>Configurable</code> to find a
+	 *            <code>Configuration</code> for
+	 * @param profile
+	 *            the <code>Profile</code> to look for the
+	 *            <code>Configuration</code> in
+	 * @return the list of <code>Configurations</code> for a
+	 *         <code>Configurable</code> in the given <code>Profile</code>
+	 */
+	public List<Configuration> configurationsFor(Configurable configurable,
+			Profile profile) {
+		List<Configuration> configurations = new ArrayList<>();
+		for (Configuration config : profile.getConfigurations())
+			if (configurable.equals(config.getConfigures()))
+				configurations.add(config);
+		// Collections.sort(configurations);
+		return configurations;
+	}
+
+	@SuppressWarnings("unchecked")
+	public List<BlockingControlLink> controlLinksBlocking(Processor blocked) {
+		List<BlockingControlLink> controlLinks = new ArrayList<>();
+		for (ControlLink link : blocked.getParent().getControlLinks()) {
+			if (!(link instanceof BlockingControlLink))
+				continue;
+			BlockingControlLink blockingControlLink = (BlockingControlLink) link;
+			if (blockingControlLink.getBlock().equals(blocked))
+				controlLinks.add(blockingControlLink);
+		}
+		Collections.sort(controlLinks);
+		return controlLinks;
+	}
+
+	@SuppressWarnings("unchecked")
+	public List<BlockingControlLink> controlLinksWaitingFor(
+			Processor untilFinished) {
+		List<BlockingControlLink> controlLinks = new ArrayList<>();
+		for (ControlLink link : untilFinished.getParent().getControlLinks()) {
+			if (!(link instanceof BlockingControlLink))
+				continue;
+			BlockingControlLink blockingControlLink = (BlockingControlLink) link;
+			if (blockingControlLink.getUntilFinished().equals(untilFinished))
+				controlLinks.add(blockingControlLink);
+		}
+		Collections.sort(controlLinks);
+		return controlLinks;
+	}
+
+	@SuppressWarnings("unchecked")
+	public List<DataLink> datalinksFrom(SenderPort senderPort) {
+		Workflow wf = findParent(Workflow.class, (Child<Workflow>) senderPort);
+		List<DataLink> links = new ArrayList<>();
+		for (DataLink link : wf.getDataLinks())
+			if (link.getReceivesFrom().equals(senderPort))
+				links.add(link);
+		Collections.sort(links);
+		return links;
+	}
+
+	@SuppressWarnings("unchecked")
+	public List<DataLink> datalinksTo(ReceiverPort receiverPort) {
+		Workflow wf = findParent(Workflow.class, (Child<Workflow>) receiverPort);
+		List<DataLink> links = new ArrayList<>();
+		for (DataLink link : wf.getDataLinks())
+			if (link.getSendsTo().equals(receiverPort))
+				links.add(link);
+		Collections.sort(links);
+		return links;
+	}
+
+	public <T extends WorkflowBean> T findParent(Class<T> parentClass,
+			Child<?> child) {
+		WorkflowBean parent = child.getParent();
+		if (parent == null)
+			return null;
+		if (parentClass.isAssignableFrom(parent.getClass())) {
+			@SuppressWarnings("unchecked")
+			T foundParent = (T) parent;
+			return foundParent;
+		}
+		if (parent instanceof Child)
+			return findParent(parentClass, (Child<?>) parent);
+		return null;
+	}
+
+	public JsonNode portDefinitionFor(ActivityPort activityPort, Profile profile)
+			throws PropertyException {
+		Configuration actConfig = configurationFor(activityPort.getParent(),
+				profile);
+
+		JsonNode portDef = actConfig.getJson().get("portDefinition");
+		if (portDef == null)
+			return null;
+
+		URI portPath = uriTools.relativeUriForBean(activityPort,
+				activityPort.getParent());
+		// e.g. "in/input1" or "out/output2"
+		return portDef.get(portPath.toString());
+	}
+
+	public ProcessorBinding processorBindingForProcessor(Processor processor,
+			Profile profile) {
+		List<ProcessorBinding> bindings = processorBindingsForProcessor(
+				processor, profile);
+		if (bindings.isEmpty())
+			throw new IndexOutOfBoundsException("Could not find bindings for "
+					+ processor);
+		if (bindings.size() > 1)
+			throw new IllegalStateException("More than one proc binding for "
+					+ processor);
+		return bindings.get(0);
+	}
+
+	public List<ProcessorBinding> processorBindingsForProcessor(
+			Processor processor, Profile profile) {
+		List<ProcessorBinding> bindings = new ArrayList<>();
+		for (ProcessorBinding pb : profile.getProcessorBindings())
+			if (pb.getBoundProcessor().equals(processor))
+				bindings.add(pb);
+		Collections.sort(bindings, new BindingComparator());
+		return bindings;
+	}
+
+	public List<ProcessorBinding> processorBindingsToActivity(Activity activity) {
+		Profile profile = activity.getParent();
+		List<ProcessorBinding> bindings = new ArrayList<>();
+		for (ProcessorBinding pb : profile.getProcessorBindings())
+			if (pb.getBoundActivity().equals(activity))
+				bindings.add(pb);
+		Collections.sort(bindings, new BindingComparator());
+		return bindings;
+	}
+
+	public ProcessorInputPortBinding processorPortBindingForPort(
+			InputPort inputPort, Profile profile) {
+		return (ProcessorInputPortBinding) processorPortBindingForPortInternal(
+				inputPort, profile);
+	}
+
+	public ProcessorOutputPortBinding processorPortBindingForPort(
+			OutputPort outputPort, Profile profile) {
+		return (ProcessorOutputPortBinding) processorPortBindingForPortInternal(
+				outputPort, profile);
+	}
+
+	protected ProcessorPortBinding<?, ?> processorPortBindingForPortInternal(
+			Port port, Profile profile) {
+		List<ProcessorBinding> processorBindings;
+		if (port instanceof ProcessorPort) {
+			ProcessorPort processorPort = (ProcessorPort) port;
+			processorBindings = processorBindingsForProcessor(
+					processorPort.getParent(), profile);
+		} else if (port instanceof ActivityPort) {
+			ActivityPort activityPort = (ActivityPort) port;
+			processorBindings = processorBindingsToActivity(activityPort
+					.getParent());
+		} else
+			throw new IllegalArgumentException(
+					"Port must be a ProcessorPort or ActivityPort");
+		for (ProcessorBinding procBinding : processorBindings) {
+			ProcessorPortBinding<?, ?> portBinding = processorPortBindingInternalInBinding(
+					port, procBinding);
+			if (portBinding != null)
+				return portBinding;
+		}
+		return null;
+	}
+
+	protected ProcessorPortBinding<?, ?> processorPortBindingInternalInBinding(
+			Port port, ProcessorBinding procBinding) {
+		Set<? extends ProcessorPortBinding<?, ?>> portBindings;
+		if (port instanceof InputPort)
+			portBindings = procBinding.getInputPortBindings();
+		else
+			portBindings = procBinding.getOutputPortBindings();
+
+		for (ProcessorPortBinding<?, ?> portBinding : portBindings) {
+			if (port instanceof ProcessorPort
+					&& portBinding.getBoundProcessorPort().equals(port))
+				return portBinding;
+			if (port instanceof ActivityPort
+					&& portBinding.getBoundActivityPort().equals(port))
+				return portBinding;
+		}
+		return null;
+	}
+
+	public void setParents(WorkflowBundle bundle) {
+		bundle.accept(new VisitorWithPath() {
+			@Override
+			public boolean visit() {
+				WorkflowBean node = getCurrentNode();
+				if (node instanceof Child) {
+					@SuppressWarnings("unchecked")
+					Child<WorkflowBean> child = (Child<WorkflowBean>) node;
+					WorkflowBean parent = getCurrentPath().peek();
+					if (child.getParent() != parent)
+						child.setParent(parent);
+				}
+				return true;
+			}
+		});
+	}
+
+	/**
+	 * Find processors that a given processor can connect to downstream.
+	 * <p>
+	 * This is calculated as all processors in the dataflow, except the
+	 * processor itself, and any processor <em>upstream</em>, following both
+	 * data links and conditional links.
+	 * 
+	 * @see #possibleUpStreamProcessors(Dataflow, Processor)
+	 * @see #splitProcessors(Collection, Processor)
+	 * 
+	 * @param dataflow
+	 *            Dataflow from where to find processors
+	 * @param processor
+	 *            Processor which is to be connected
+	 * @return A set of possible downstream processors
+	 */
+	public Set<Processor> possibleDownStreamProcessors(Workflow dataflow,
+			Processor processor) {
+		ProcessorSplit splitProcessors = splitProcessors(
+				dataflow.getProcessors(), processor);
+		Set<Processor> possibles = new HashSet<>(
+				splitProcessors.getUnconnected());
+		possibles.addAll(splitProcessors.getDownStream());
+		return possibles;
+	}
+
+	/**
+	 * Find processors that a given processor can connect to upstream.
+	 * <p>
+	 * This is calculated as all processors in the dataflow, except the
+	 * processor itself, and any processor <em>downstream</em>, following both
+	 * data links and conditional links.
+	 * 
+	 * @see #possibleDownStreamProcessors(Dataflow, Processor)
+	 * @see #splitProcessors(Collection, Processor)
+	 * 
+	 * @param dataflow
+	 *            Dataflow from where to find processors
+	 * @param processor
+	 *            Processor which is to be connected
+	 * @return A set of possible downstream processors
+	 */
+	public Set<Processor> possibleUpStreamProcessors(Workflow dataflow,
+			Processor firstProcessor) {
+		ProcessorSplit splitProcessors = splitProcessors(
+				dataflow.getProcessors(), firstProcessor);
+		Set<Processor> possibles = new HashSet<>(
+				splitProcessors.getUnconnected());
+		possibles.addAll(splitProcessors.getUpStream());
+		return possibles;
+	}
+
+	/**
+	 * @param processors
+	 * @param splitPoint
+	 * @return
+	 */
+	public ProcessorSplit splitProcessors(Collection<Processor> processors,
+			Processor splitPoint) {
+		Set<Processor> upStream = new HashSet<>();
+		Set<Processor> downStream = new HashSet<>();
+		Set<Processor> queue = new HashSet<>();
+
+		queue.add(splitPoint);
+
+		// First let's go upstream
+		while (!queue.isEmpty()) {
+			Processor processor = queue.iterator().next();
+			queue.remove(processor);
+			List<BlockingControlLink> preConditions = controlLinksBlocking(processor);
+			for (BlockingControlLink condition : preConditions) {
+				Processor upstreamProc = condition.getUntilFinished();
+				if (!upStream.contains(upstreamProc)) {
+					upStream.add(upstreamProc);
+					queue.add(upstreamProc);
+				}
+			}
+			for (InputProcessorPort inputPort : processor.getInputPorts())
+				for (DataLink incomingLink : datalinksTo(inputPort)) {
+					SenderPort source = incomingLink.getReceivesFrom();
+					if (!(source instanceof OutputProcessorPort))
+						continue;
+					Processor upstreamProc = ((OutputProcessorPort) source)
+							.getParent();
+					if (!upStream.contains(upstreamProc)) {
+						upStream.add(upstreamProc);
+						queue.add(upstreamProc);
+					}
+				}
+		}
+		// Our split
+		queue.add(splitPoint);
+		// Then downstream
+		while (!queue.isEmpty()) {
+			Processor processor = queue.iterator().next();
+			queue.remove(processor);
+			List<BlockingControlLink> controlledConditions = controlLinksWaitingFor(processor);
+			for (BlockingControlLink condition : controlledConditions) {
+				Processor downstreamProc = condition.getBlock();
+				if (!downStream.contains(downstreamProc)) {
+					downStream.add(downstreamProc);
+					queue.add(downstreamProc);
+				}
+			}
+			for (OutputProcessorPort outputPort : processor.getOutputPorts())
+				for (DataLink datalink : datalinksFrom(outputPort)) {
+					ReceiverPort sink = datalink.getSendsTo();
+					if (!(sink instanceof InputProcessorPort))
+						continue;
+					Processor downstreamProcc = ((InputProcessorPort) sink)
+							.getParent();
+					if (!downStream.contains(downstreamProcc)) {
+						downStream.add(downstreamProcc);
+						queue.add(downstreamProcc);
+					}
+				}
+		}
+		Set<Processor> undecided = new HashSet<>(processors);
+		undecided.remove(splitPoint);
+		undecided.removeAll(upStream);
+		undecided.removeAll(downStream);
+		return new ProcessorSplit(splitPoint, upStream, downStream, undecided);
+	}
+
+	/**
+	 * Result bean returned from
+	 * {@link Scufl2Tools#splitProcessors(Collection, Processor)}.
+	 * 
+	 * @author Stian Soiland-Reyes
+	 */
+	public static class ProcessorSplit {
+		private final Processor splitPoint;
+		private final Set<Processor> upStream;
+		private final Set<Processor> downStream;
+		private final Set<Processor> unconnected;
+
+		/**
+		 * Processor that was used as a split point.
+		 * 
+		 * @return Split point processor
+		 */
+		public Processor getSplitPoint() {
+			return splitPoint;
+		}
+
+		/**
+		 * Processors that are upstream from the split point.
+		 * 
+		 * @return Upstream processors
+		 */
+		public Set<Processor> getUpStream() {
+			return upStream;
+		}
+
+		/**
+		 * Processors that are downstream from the split point.
+		 * 
+		 * @return Downstream processors
+		 */
+		public Set<Processor> getDownStream() {
+			return downStream;
+		}
+
+		/**
+		 * Processors that are unconnected to the split point.
+		 * <p>
+		 * These are processors in the dataflow that are neither upstream,
+		 * downstream or the split point itself.
+		 * <p>
+		 * Note that this does not imply a total graph separation, for instance
+		 * processors in {@link #getUpStream()} might have some of these
+		 * unconnected processors downstream, but not along the path to the
+		 * {@link #getSplitPoint()}, or they could be upstream from any
+		 * processor in {@link #getDownStream()}.
+		 * 
+		 * @return Processors unconnected from the split point
+		 */
+		public Set<Processor> getUnconnected() {
+			return unconnected;
+		}
+
+		/**
+		 * Construct a new processor split result.
+		 * 
+		 * @param splitPoint
+		 *            Processor used as split point
+		 * @param upStream
+		 *            Processors that are upstream from split point
+		 * @param downStream
+		 *            Processors that are downstream from split point
+		 * @param unconnected
+		 *            The rest of the processors, that are by definition
+		 *            unconnected to split point
+		 */
+		public ProcessorSplit(Processor splitPoint, Set<Processor> upStream,
+				Set<Processor> downStream, Set<Processor> unconnected) {
+			this.splitPoint = splitPoint;
+			this.upStream = upStream;
+			this.downStream = downStream;
+			this.unconnected = unconnected;
+		}
+	}
+
+	/**
+	 * Return nested workflow for processor as configured in given profile.
+	 * <p>
+	 * A nested workflow is an activity bound to the processor with the
+	 * configurable type equal to {@value #NESTED_WORKFLOW}.
+	 * <p>
+	 * This method returns <code>null</code> if no such workflow was found,
+	 * otherwise the configured workflow.
+	 * <p
+	 * Note that even if several bindings/configurations map to a different
+	 * workflow, this method throws an IllegalStateException. Most workflows
+	 * will only have a single workflow for a given profile, to handle more
+	 * complex cases use instead
+	 * {@link #nestedWorkflowsForProcessor(Processor, Profile)}.
+	 * 
+	 * @throws NullPointerException
+	 *             if the given profile does not have a parent
+	 * @throws IllegalStateException
+	 *             if a nested workflow configuration is invalid, or more than
+	 *             one possible workflow is found
+	 * 
+	 * @param processor
+	 *            Processor which might have a nested workflow
+	 * @param profile
+	 *            Profile to look for nested workflow activity/configuration.
+	 *            The profile must have a {@link WorkflowBundle} set as its
+	 *            {@link Profile#setParent(WorkflowBundle)}.
+	 * @return The configured nested workflows for processor
+	 */
+	public Workflow nestedWorkflowForProcessor(Processor processor,
+			Profile profile) {
+		List<Workflow> wfs = nestedWorkflowsForProcessor(processor, profile);
+		if (wfs.isEmpty())
+			return null;
+		if (wfs.size() > 1)
+			throw new IllegalStateException(
+					"More than one possible workflow for processor "
+							+ processor);
+		return wfs.get(0);
+	}
+
+	/**
+	 * Return list of nested workflows for processor as configured in given
+	 * profile.
+	 * <p>
+	 * A nested workflow is an activity bound to the processor with the
+	 * configurable type equal to {@value #NESTED_WORKFLOW}.
+	 * <p>
+	 * This method returns a list of 0 or more workflows, as every matching
+	 * {@link ProcessorBinding} and every matching {@link Configuration} for the
+	 * bound activity is considered. Normally there will only be a single nested
+	 * workflow, in which case the
+	 * {@link #nestedWorkflowForProcessor(Processor, Profile)} method should be
+	 * used instead.
+	 * <p>
+	 * Note that even if several bindings/configurations map to the same
+	 * workflow, each workflow is only included once in the list. Nested
+	 * workflow configurations that are incomplete or which #workflow can't be
+	 * found within the workflow bundle of the profile will be silently ignored.
+	 * 
+	 * @throws NullPointerException
+	 *             if the given profile does not have a parent
+	 * @throws IllegalStateException
+	 *             if a nested workflow configuration is invalid
+	 * 
+	 * @param processor
+	 *            Processor which might have a nested workflow
+	 * @param profile
+	 *            Profile to look for nested workflow activity/configuration.
+	 *            The profile must have a {@link WorkflowBundle} set as its
+	 *            {@link Profile#setParent(WorkflowBundle)}.
+	 * @return List of configured nested workflows for processor
+	 */
+	public List<Workflow> nestedWorkflowsForProcessor(Processor processor,
+			Profile profile) {
+		WorkflowBundle bundle = profile.getParent();
+		if (bundle == null)
+			throw new NullPointerException("Parent must be set for " + profile);
+		ArrayList<Workflow> workflows = new ArrayList<>();
+		for (ProcessorBinding binding : processorBindingsForProcessor(
+				processor, profile)) {
+			if (!binding.getBoundActivity().getType().equals(NESTED_WORKFLOW))
+				continue;
+			for (Configuration c : configurationsFor(
+					binding.getBoundActivity(), profile)) {
+				JsonNode nested = c.getJson().get("nestedWorkflow");
+				Workflow wf = bundle.getWorkflows().getByName(nested.asText());
+				if (wf != null && !workflows.contains(wf))
+					workflows.add(wf);
+			}
+		}
+		return workflows;
+	}
+
+	/**
+	 * Returns true if processor contains a nested workflow in any of its
+	 * activities in any of its profiles.
+	 */
+	public boolean containsNestedWorkflow(Processor processor) {
+		for (Profile profile : processor.getParent().getParent().getProfiles())
+			if (containsNestedWorkflow(processor, profile))
+				return true;
+		return false;
+	}
+
+	/**
+	 * Returns true if processor contains a nested workflow in the specified
+	 * profile.
+	 */
+	public boolean containsNestedWorkflow(Processor processor, Profile profile) {
+		for (ProcessorBinding binding : processorBindingsForProcessor(
+				processor, profile))
+			if (binding.getBoundActivity().getType().equals(NESTED_WORKFLOW))
+				return true;
+		return false;
+	}
+
+	public void createActivityPortsFromProcessor(Activity activity,
+			Processor processor) {
+		for (InputProcessorPort processorPort : processor.getInputPorts())
+			new InputActivityPort(activity, processorPort.getName())
+					.setDepth(processorPort.getDepth());
+		for (OutputProcessorPort processorPort : processor.getOutputPorts()) {
+			OutputActivityPort activityPort = new OutputActivityPort(activity,
+					processorPort.getName());
+			activityPort.setDepth(processorPort.getDepth());
+			activityPort.setGranularDepth(processorPort.getGranularDepth());
+		}
+	}
+
+	public void createProcessorPortsFromActivity(Processor processor,
+			Activity activity) {
+		for (InputActivityPort activityPort : activity.getInputPorts())
+			new InputProcessorPort(processor, activityPort.getName())
+					.setDepth(activityPort.getDepth());
+		for (OutputActivityPort activityPort : activity.getOutputPorts()) {
+			OutputProcessorPort procPort = new OutputProcessorPort(processor,
+					activityPort.getName());
+			procPort.setDepth(activityPort.getDepth());
+			procPort.setGranularDepth(activityPort.getGranularDepth());
+		}
+	}
+
+	public ProcessorBinding bindActivityToProcessorByMatchingPorts(
+			Activity activity, Processor processor) {
+		ProcessorBinding binding = new ProcessorBinding();
+		binding.setParent(activity.getParent());
+		binding.setBoundActivity(activity);
+		binding.setBoundProcessor(processor);
+		bindActivityToProcessorByMatchingPorts(binding);
+		return binding;
+	}
+
+	public void bindActivityToProcessorByMatchingPorts(ProcessorBinding binding) {
+		Activity activity = binding.getBoundActivity();
+		Processor processor = binding.getBoundProcessor();
+		for (InputActivityPort activityPort : activity.getInputPorts()) {
+			InputProcessorPort processorPort = processor.getInputPorts()
+					.getByName(activityPort.getName());
+			if (processorPort != null
+					&& processorPortBindingInternalInBinding(processorPort,
+							binding) == null)
+				new ProcessorInputPortBinding(binding, processorPort,
+						activityPort);
+		}
+
+		for (OutputProcessorPort processorPort : processor.getOutputPorts()) {
+			OutputActivityPort activityPort = activity.getOutputPorts()
+					.getByName(processorPort.getName());
+			if (activityPort != null
+					&& processorPortBindingInternalInBinding(activityPort,
+							binding) == null)
+				new ProcessorOutputPortBinding(binding, activityPort,
+						processorPort);
+		}
+	}
+
+	public ProcessorBinding createProcessorAndBindingFromActivity(
+			Activity activity) {
+		Processor proc = new Processor();
+		proc.setName(activity.getName());
+		createProcessorPortsFromActivity(proc, activity);
+		return bindActivityToProcessorByMatchingPorts(activity, proc);
+	}
+
+	public Activity createActivityFromProcessor(Processor processor,
+			Profile profile) {
+		Activity activity = new Activity();
+		activity.setName(processor.getName());
+		activity.setParent(profile);
+		createActivityPortsFromProcessor(activity, processor);
+		bindActivityToProcessorByMatchingPorts(activity, processor);
+		return activity;
+	}
+
+	public void removePortsBindingForUnknownPorts(ProcessorBinding binding) {
+		// First, remove ports no longer owned by processor
+		Iterator<ProcessorInputPortBinding> inputBindings = binding
+				.getInputPortBindings().iterator();
+		Activity activity = binding.getBoundActivity();
+		Processor processor = binding.getBoundProcessor();
+		for (ProcessorInputPortBinding ip : iterable(inputBindings)) {
+			if (!activity.getInputPorts().contains(ip.getBoundActivityPort())) {
+				inputBindings.remove();
+				continue;
+			}
+			if (!processor.getInputPorts().contains(ip.getBoundProcessorPort())) {
+				inputBindings.remove();
+				continue;
+			}
+		}
+		Iterator<ProcessorOutputPortBinding> outputBindings = binding
+				.getOutputPortBindings().iterator();
+		for (ProcessorOutputPortBinding op : iterable(outputBindings)) {
+			if (!activity.getOutputPorts().contains(op.getBoundActivityPort())) {
+				outputBindings.remove();
+				continue;
+			}
+			if (!processor.getOutputPorts()
+					.contains(op.getBoundProcessorPort())) {
+				outputBindings.remove();
+				continue;
+			}
+		}
+	}
+
+	public void updateBindingByMatchingPorts(ProcessorBinding binding) {
+		removePortsBindingForUnknownPorts(binding);
+		bindActivityToProcessorByMatchingPorts(binding);
+	}
+
+	private <T> Iterable<T> iterable(final Iterator<T> it) {
+		return new Iterable<T>() {
+			@Override
+			public Iterator<T> iterator() {
+				return it;
+			}
+		};
+	}
+
+	public static URI CONSTANT = URI
+			.create("http://ns.taverna.org.uk/2010/activity/constant");
+
+	public static URI CONSTANT_CONFIG = CONSTANT.resolve("#Config");
+
+	public Processor createConstant(Workflow workflow, Profile profile,
+			String name) {
+		Processor processor = new Processor(null, name);
+		workflow.getProcessors().addWithUniqueName(processor);
+		processor.setParent(workflow);
+		OutputProcessorPort valuePort = new OutputProcessorPort(processor,
+				CONSTANT_VALUE_PORT);
+		valuePort.setDepth(0);
+		valuePort.setGranularDepth(0);
+
+		Activity activity = createActivityFromProcessor(processor, profile);
+		activity.setType(CONSTANT);
+		createConfigurationFor(activity, CONSTANT_CONFIG);
+		return processor;
+	}
+
+	public Configuration createConfigurationFor(Activity activity,
+			URI configType) {
+		Profile profile = activity.getParent();
+
+		Configuration config = new Configuration(activity.getName());
+		profile.getConfigurations().addWithUniqueName(config);
+		config.setParent(profile);
+
+		config.setConfigures(activity);
+		config.setType(configType);
+		return config;
+	}
+
+	public Configuration createConfigurationFor(Processor processor,
+			Profile profile) {
+		Configuration config = new Configuration(processor.getName() + "-proc");
+		profile.getConfigurations().addWithUniqueName(config);
+		config.setParent(profile);
+		config.setConfigures(processor);
+		config.setType(Processor.CONFIG_TYPE);
+		return config;
+	}
+
+	public void setConstantStringValue(Processor constant, String value,
+			Profile profile) {
+		Configuration config = configurationForActivityBoundToProcessor(
+				constant, profile);
+		config.getJsonAsObjectNode().put(CONSTANT_STRING, value);
+	}
+
+	public String getConstantStringValue(Processor constant, Profile profile) {
+		Configuration config = configurationForActivityBoundToProcessor(
+				constant, profile);
+		return config.getJson().get(CONSTANT_STRING).asText();
+	}
+
+	public Set<Processor> getConstants(Workflow workflow, Profile profile) {
+		Set<Processor> procs = new LinkedHashSet<>();
+		for (Configuration config : profile.getConfigurations()) {
+			Configurable configurable = config.getConfigures();
+			if (!CONSTANT.equals(configurable.getType())
+					|| !(configurable instanceof Activity))
+				continue;
+			for (ProcessorBinding bind : processorBindingsToActivity((Activity) configurable))
+				procs.add(bind.getBoundProcessor());
+		}
+		return procs;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Typed.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Typed.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Typed.java
new file mode 100644
index 0000000..b180f71
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/common/Typed.java
@@ -0,0 +1,45 @@
+package org.apache.taverna.scufl2.api.common;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import java.net.URI;
+
+/**
+ * A typed {@link WorkflowBean}.
+ * 
+ * @author Stian Soiland-Reyes
+ */
+public interface Typed extends WorkflowBean {
+	/**
+	 * Returns the type of the {@link WorkflowBean}.
+	 * 
+	 * @return the type of the <code>WorkflowBean</code>
+	 */
+	URI getType();
+
+	/**
+	 * Sets the type of the {@link WorkflowBean}.
+	 * 
+	 * @param type
+	 *            the type of the <code>WorkflowBean</code>.
+	 */
+	void setType(URI type);
+}