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);
+}