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:17 UTC
[32/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/core/Workflow.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/core/Workflow.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/core/Workflow.java
new file mode 100644
index 0000000..0b101c5
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/core/Workflow.java
@@ -0,0 +1,283 @@
+package org.apache.taverna.scufl2.api.core;
+
+/*
+ * 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.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.taverna.scufl2.api.annotation.Revisioned;
+import org.apache.taverna.scufl2.api.common.AbstractRevisioned;
+import org.apache.taverna.scufl2.api.common.Child;
+import org.apache.taverna.scufl2.api.common.NamedSet;
+import org.apache.taverna.scufl2.api.common.Ported;
+import org.apache.taverna.scufl2.api.common.Visitor;
+import org.apache.taverna.scufl2.api.common.WorkflowBean;
+import org.apache.taverna.scufl2.api.container.WorkflowBundle;
+import org.apache.taverna.scufl2.api.port.InputWorkflowPort;
+import org.apache.taverna.scufl2.api.port.OutputWorkflowPort;
+
+
+/**
+ * A <code>Workflow</code> is a set of {@link Processor}s and {@link DataLink}s
+ * between the <code>Processor</code>s. <code>Workflow</code>s may also have
+ * input and output ports.
+ *
+ * @author Alan R Williams
+ * @author Stian Soiland-Reyes
+ */
+public class Workflow extends AbstractRevisioned implements
+ Child<WorkflowBundle>, Ported, Revisioned {
+ public static final URI WORKFLOW_ROOT = URI
+ .create("http://ns.taverna.org.uk/2010/workflow/");
+
+ private final TreeSet<DataLink> dataLinks = new TreeSet<>();
+ private final TreeSet<ControlLink> controlLinks = new TreeSet<>();
+ private final NamedSet<InputWorkflowPort> inputPorts = new NamedSet<>();
+ private final NamedSet<OutputWorkflowPort> outputPorts = new NamedSet<>();
+ private final NamedSet<Processor> processors = new NamedSet<>();
+ private WorkflowBundle parent;
+
+ /**
+ * Constructs a <code>Workflow</code> with a name based on a random UUID.
+ */
+ public Workflow() {
+ }
+
+ /**
+ * Constructs a <code>Workflow</code> with the specified name.
+ *
+ * @param name
+ * The name of the <code>Workflow</code>. <strong>Must
+ * not</strong> be <code>null</code> or an empty String.
+ */
+ public Workflow(String name) {
+ super(name);
+ }
+
+ @Override
+ public boolean accept(Visitor visitor) {
+ if (visitor.visitEnter(this)) {
+ List<WorkflowBean> children = new ArrayList<>();
+ children.addAll(getInputPorts());
+ children.addAll(getOutputPorts());
+ children.addAll(getProcessors());
+ children.addAll(getDataLinks());
+ children.addAll(getControlLinks());
+ children.add(getCurrentRevision());
+ for (WorkflowBean bean : children)
+ if (!bean.accept(visitor))
+ break;
+ }
+ return visitor.visitLeave(this);
+ }
+
+ /**
+ * Returns the <code>ControlLink</code>s.
+ *
+ * If there are no <code>ControlLink</code>s an empty set is returned.
+ *
+ * @return the <code>ControlLink</code>s
+ */
+ public Set<ControlLink> getControlLinks() {
+ return controlLinks;
+ }
+
+ /**
+ * Returns the <code>DataLink</code>s.
+ *
+ * If there are no <code>DataLink</code>s an empty set is returned.
+ *
+ * @return the <code>DataLink</code>s.
+ */
+ public Set<DataLink> getDataLinks() {
+ return dataLinks;
+ }
+
+ /**
+ * Returns the <code>InputWorkflowPort</code>s.
+ *
+ * If there are no <code>InputWorkflowPort</code>s an empty set is returned.
+ *
+ * @return the <code>InputWorkflowPort</code>s.
+ */
+ @Override
+ public NamedSet<InputWorkflowPort> getInputPorts() {
+ return inputPorts;
+ }
+
+ /**
+ * Returns the <code>OutputWorkflowPort</code>s.
+ *
+ * If there are no <code>OutputWorkflowPort</code>s an empty set is
+ * returned.
+ *
+ * @return the <code>OutputWorkflowPort</code>s.
+ */
+ @Override
+ public NamedSet<OutputWorkflowPort> getOutputPorts() {
+ return outputPorts;
+ }
+
+ @Override
+ public WorkflowBundle getParent() {
+ return parent;
+ }
+
+ /**
+ * Returns the <code>Processor</code>s.
+ *
+ * If there are no <code>Processor</code>s an empty set is returned.
+ *
+ * @return the <code>Processor</code>s.
+ */
+ public NamedSet<Processor> getProcessors() {
+ return processors;
+ }
+
+ /**
+ * Set the <code>ControlLink</code>s to be the contents of the specified
+ * set.
+ * <p>
+ * <code>ControlLink</code>s can be added by using
+ * {@link #getControlLinks()}.add(controlLink).
+ *
+ * @param controlLinks
+ * the <code>ControlLink</code>s. <strong>Must not</strong> be
+ * null
+ */
+ public void setControlLinks(Set<ControlLink> controlLinks) {
+ this.controlLinks.clear();
+ this.controlLinks.addAll(controlLinks);
+ }
+
+ /**
+ * Set the <code>DataLink</code>s to be the contents of the specified set.
+ * <p>
+ * <code>DataLink</code>s can be added by using {@link #getDataLinks()}
+ * .add(dataLink).
+ *
+ * @param dataLinks
+ * the <code>DataLink</code>s. <strong>Must not</strong> be null
+ */
+ public void setDataLinks(Set<DataLink> dataLinks) {
+ this.dataLinks.clear();
+ this.dataLinks.addAll(dataLinks);
+ }
+
+ /**
+ * Set the <code>InputWorkflowPort</code>s to be the contents of the
+ * specified set.
+ * <p>
+ * <code>InputWorkflowPort</code>s can be added by using
+ * {@link #getInputWorkflowPorts()}.add(inputPort).
+ *
+ * @param inputPorts
+ * the <code>InputWorkflowPort</code>s. <strong>Must not</strong>
+ * be null
+ */
+ public void setInputPorts(Set<InputWorkflowPort> inputPorts) {
+ this.inputPorts.clear();
+ for (InputWorkflowPort inputPort : inputPorts)
+ inputPort.setParent(this);
+ }
+
+ /**
+ * Set the <code>OutputWorkflowPort</code>s to be the contents of the
+ * specified set.
+ * <p>
+ * <code>OutputWorkflowPort</code>s can be added by using
+ * {@link #getOutputWorkflowPorts()}.add(outputPort).
+ *
+ * @param outputPorts
+ * the <code>OutputWorkflowPort</code>s. <strong>Must
+ * not</strong> be null
+ */
+ public void setOutputPorts(Set<OutputWorkflowPort> outputPorts) {
+ this.outputPorts.clear();
+ for (OutputWorkflowPort outputPort : outputPorts)
+ outputPort.setParent(this);
+ }
+
+ @Override
+ public void setParent(WorkflowBundle parent) {
+ if (this.parent != null && this.parent != parent)
+ this.parent.getWorkflows().remove(this);
+ this.parent = parent;
+ if (parent != null)
+ parent.getWorkflows().add(this);
+ }
+
+ /**
+ * Set the <code>Processor</code>s to be the contents of the specified set.
+ * <p>
+ * <code>Processor</code>s can be added by using {@link #getProcessors()}
+ * .add(processor).
+ *
+ * @param processors
+ * the <code>Processor</code>s. <strong>Must not</strong> be null
+ */
+ public void setProcessors(Set<Processor> processors) {
+ this.processors.clear();
+ for (Processor processor : processors)
+ processor.setParent(this);
+ }
+
+ /**
+ * Updates the workflow identifier.
+ * <p>
+ * The {@link #getCurrentRevision()} will be replaced using using
+ * {@link #newRevision()}.
+ *
+ */
+ public void updateWorkflowIdentifier() {
+ newRevision();
+ }
+
+ @SuppressWarnings("unused")
+ private String toString(Collection<?> collection, int maxLen) {
+ StringBuilder builder = new StringBuilder("[");
+ String sep = "";
+ int i = 0;
+ for (Object o : collection) {
+ builder.append(sep).append(o);
+ sep = ", ";
+ if (++i >= maxLen)
+ break;
+ }
+ return builder.append("]").toString();
+ }
+
+ @Override
+ protected URI getIdentifierRoot() {
+ return WORKFLOW_ROOT;
+ }
+
+ @Override
+ protected void cloneInto(WorkflowBean clone, Cloning cloning) {
+ super.cloneInto(clone, cloning);
+ Workflow cloneWorkflow = (Workflow)clone;
+ cloneWorkflow.setCurrentRevision(cloning.cloneIfNotInCache(getCurrentRevision()));
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/core/package-info.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/core/package-info.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/core/package-info.java
new file mode 100644
index 0000000..5061365
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/core/package-info.java
@@ -0,0 +1,22 @@
+package org.apache.taverna.scufl2.api.core;
+
+/*
+ * 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/impl/IterableComparator.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/impl/IterableComparator.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/impl/IterableComparator.java
new file mode 100644
index 0000000..d7b3972
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/impl/IterableComparator.java
@@ -0,0 +1,116 @@
+package org.apache.taverna.scufl2.api.impl;
+
+/*
+ * 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.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+/**
+ * A Comparator of Iterables of comparables.
+ * <p>
+ * This comparator compares two {@link Iterable}s by comparing their items.
+ * Iterables are compared on the first items, then on the second items, etc. If
+ * Iterable A is larger than Iterable B, but all the items of B compared 0
+ * (equal) with the corresponding items in A, then the comparator will sort the
+ * smaller Iterable before the bigger Iterable.
+ * <p>
+ * Obviously this comparator would not give predictable results for
+ * {@link Iterable}s which don't have a predictable iteration order, like
+ * {@link HashSet}, but should work well with {@link List}s,
+ * {@link LinkedHashSet}, etc.
+ * <p>
+ * The two Iterables don't need to contain items of the same classes, as long as
+ * their element's {@link Comparable#compareTo(Object)} allow comparison. (For
+ * instance a Iterable<String> would not compare against
+ * Iterable<Integer>). As according to
+ * {@link Comparator#compare(Object, Object)} a {@link ClassCastException} would
+ * be thrown in such cases.
+ * <p>
+ * Items in the Iterable are compared using {@link NullSafeComparator}, so this
+ * comparator will handle Iterables containing <code>null</code> items
+ * predictably. This comparator also allows <code>null</code> in the same way,
+ * so comparison of a {@link Iterable} and <code>null</code> will sort
+ * <code>null</code> before the Iterable.
+ * <p>
+ * If an item in the Iterable is a {@link Iterable} which is not itself
+ * {@link Comparable}, and it is being compared with a different Iterable, it
+ * will be recursively compared with the same instance of
+ * {@link IterableComparator}. However, if such {@link Iterable}s are compared
+ * with other {@link Comparable}s a {@link ClassCastException} would be thrown.
+ * <p>
+ * If an item in a Iterable is not {@link Comparable} <code>null</code> and
+ * could not be recursively compared, a {@link ClassCastException} will be
+ * thrown, as according to {@link Comparator#compare(Object, Object)}
+ *
+ * @author Stian Soiland-Reyes
+ */
+public class IterableComparator implements Comparator<Iterable<?>> {
+ @Override
+ public int compare(Iterable<?> a, Iterable<?> b) {
+ Integer nullCompare = compareNulls(a, b);
+ if (nullCompare != null)
+ return nullCompare;
+
+ Iterator<?> aIt = a.iterator();
+ Iterator<?> bIt = b.iterator();
+
+ while (aIt.hasNext() && bIt.hasNext()) {
+ int itemCompare = compareItems(aIt.next(), bIt.next());
+ if (itemCompare != 0)
+ return itemCompare;
+ }
+ // Puh, compared all corresponding items
+
+ if (aIt.hasNext())
+ return 1; // a is bigger
+ if (bIt.hasNext())
+ return -1; // a is smaller
+ // Both finished? Then we are equal!
+ return 0;
+ }
+
+ @SuppressWarnings("unchecked")
+ protected int compareItems(Object a, Object b) {
+ Integer nullCompare = compareNulls(a, b);
+ if (nullCompare != null)
+ return nullCompare;
+ if (a instanceof Comparable && b instanceof Comparable)
+ return ((Comparable<Object>) a).compareTo((Comparable<Object>) b);
+ if (a instanceof Iterable && b instanceof Iterable)
+ // Recurse
+ return compare((Iterable<?>) a, (Iterable<?>) b);
+ throw new ClassCastException(
+ "Compared items must be null, or both be Comparable or Iterables");
+ }
+
+ protected Integer compareNulls(Object a, Object b) {
+ if (a == null && b == null)
+ return 0;
+ if (a == null && b != null)
+ return -1;
+ if (a != null && b == null)
+ return 1;
+ return 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/impl/LazyMap.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/impl/LazyMap.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/impl/LazyMap.java
new file mode 100644
index 0000000..4ec5ce7
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/impl/LazyMap.java
@@ -0,0 +1,79 @@
+package org.apache.taverna.scufl2.api.impl;
+
+/*
+ * 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.Comparator;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * A lazy TreeMap, inspired by org.apache.commons.collections.map.LazyMap
+ * <p>
+ * On {@link #get(Object)}, if a key is not found in the map,
+ * {@link #getDefault(Object)} will be called to create the value for the given
+ * key. This value is subsequently inserted into the map before being returned.
+ * <p>
+ * Call {@link #containsKey(Object)} to check if a key is in the map.
+ * <p>
+ * Implementations of this class must implement {@link #getDefault(Object)} to
+ * specify the default value to create.
+ *
+ * @author Stian Soiland-Reyes
+ *
+ * @param <KeyType>
+ * Type of key
+ * @param <ValueType>
+ * Type of value
+ */
+public abstract class LazyMap<KeyType, ValueType> extends
+ TreeMap<KeyType, ValueType> implements Map<KeyType, ValueType> {
+ private static final long serialVersionUID = 3284689384208221667L;
+
+ public LazyMap() {
+ super();
+ }
+
+ public LazyMap(Comparator<? super KeyType> comparator) {
+ super(comparator);
+ }
+
+ public LazyMap(Map<? extends KeyType, ? extends ValueType> m) {
+ super(m);
+ }
+
+ public LazyMap(SortedMap<KeyType, ? extends ValueType> m) {
+ super(m);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public ValueType get(Object key) {
+ ValueType value = super.get(key);
+ if (value == null) {
+ value = getDefault((KeyType) key);
+ put((KeyType) key, value);
+ }
+ return value;
+ }
+
+ public abstract ValueType getDefault(KeyType key);
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/impl/NullSafeComparator.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/impl/NullSafeComparator.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/impl/NullSafeComparator.java
new file mode 100644
index 0000000..b8d39c3
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/impl/NullSafeComparator.java
@@ -0,0 +1,105 @@
+package org.apache.taverna.scufl2.api.impl;
+
+/*
+ * 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.Comparator;
+
+/**
+ * A Comparator that is null-safe
+ * <p>
+ * This comparator performs {@link Comparable#compareTo(Object)} if both objects
+ * are non-null, and sorts <code>null</code> to be -1 (before the non-null), and
+ * compares two <code>null</code>s as equal.
+ * </p>
+ * <p>
+ * The static method {@link #compareObjects(Object, Object)} can be used if such
+ * a comparison is needed without having to instantiate this class, for instance
+ * because it is used within a {@link Comparable#compareTo(Object)} or because
+ * the two arguments are not of an agreeable subclass <<T> of
+ * {@link Comparable} (but still can be compared).
+ *
+ * @author Stian Soiland-Reyes
+ * @param <T>
+ * The common type of the objects to be compared.
+ */
+public class NullSafeComparator<T extends Comparable<T>> implements
+ Comparator<T> {
+ /**
+ * Compare two objects for <code>null</code>ity only.
+ * <p>
+ * If both parameters are <code>null</code>, return <code>0</code>. If only a
+ * is <code>null</code>, return <code>-1</code>, if only b is
+ * <code>null</code>, return <code>1</code>. If none are <code>null</code>,
+ * return <code>null</code>.
+ * </p>
+ * The <code>null</code> return might sound counter-intuitive, but it
+ * basically means that the objects could not be compared on nullity alone,
+ * and must be further compared.
+ *
+ * @param a
+ * First object to compare
+ * @param b
+ * Second object to compare
+ * @return 0 if both are null, -1 if a is null, 1 if b is null, otherwise
+ * <code>null</code>.
+ */
+ public static Integer nullCompare(Object a, Object b) {
+ if (a == null && b == null)
+ return 0;
+ if (a == null && b != null)
+ return -1;
+ if (a != null && b == null)
+ return 1;
+ return null;
+ }
+
+ /**
+ * Compare any two objects, null-safe.
+ * <p>
+ * If any of the parameters are <code>null</code>, results are returned as
+ * for {@link #nullCompare(Object, Object)}. Otherwise,
+ * {@link Comparable#compareTo(Object)} is called on the first parameter
+ * against the second.
+ *
+ * @param a
+ * First object to compare. Must be instance of
+ * {@link Comparable}.
+ * @param b
+ * Second object to compare. Must be
+ * @return 0 if both are null, -1 if a is null, 1 if b is null, otherwise
+ * the result of a.compareTo(b).
+ * @throws ClassCastException
+ * if the specified object's type prevents it from being
+ * compared to this object.
+ */
+ @SuppressWarnings("unchecked")
+ public static int compareObjects(Object a, Object b) {
+ Integer diff = nullCompare(a, b);
+ if (diff != null)
+ return diff;
+ return ((Comparable<Object>) a).compareTo(b);
+ }
+
+ @Override
+ public int compare(T a, T b) {
+ return compareObjects(a, b);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/ReaderException.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/ReaderException.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/ReaderException.java
new file mode 100644
index 0000000..553c0ff
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/ReaderException.java
@@ -0,0 +1,74 @@
+package org.apache.taverna.scufl2.api.io;
+
+/*
+ * 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.container.WorkflowBundle;
+
+/**
+ * Thrown when there is a problem reading a {@link WorkflowBundle}
+ *
+ * @see WorkflowBundleIO#readBundle(java.io.File, String)
+ * @see WorkflowBundleIO#readBundle(java.io.InputStream, String)
+ * @see WorkflowBundleIO#readBundle(java.net.URL, String)
+ * @see WorkflowBundleReader#readBundle(java.io.File, String)
+ * @see WorkflowBundleReader#readBundle(java.io.InputStream, String)
+ */
+@SuppressWarnings("serial")
+public class ReaderException extends Exception {
+ /**
+ * Constructs an exception with no message or cause.
+ */
+ public ReaderException() {
+ }
+
+ /**
+ * Constructs an exception with the specified message and no cause.
+ *
+ * @param message
+ * details about the exception. Can be <code>null</code>
+ */
+ public ReaderException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs an exception with the specified message and cause.
+ *
+ * @param message
+ * details about the exception. Can be <code>null</code>
+ * @param cause
+ * the cause of the exception. Can be <code>null</code>
+ */
+ public ReaderException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Constructs an exception with the specified cause and and the same message
+ * as the cause (if the cause is not null).
+ *
+ * @param cause
+ * the cause of the exception. Can be <code>null</code>
+ */
+ public ReaderException(Throwable cause) {
+ super(cause);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/WorkflowBundleIO.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/WorkflowBundleIO.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/WorkflowBundleIO.java
new file mode 100644
index 0000000..d8cb24f
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/WorkflowBundleIO.java
@@ -0,0 +1,538 @@
+package org.apache.taverna.scufl2.api.io;
+
+/*
+ * 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.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Logger;
+
+import org.apache.taverna.scufl2.api.annotation.Revisioned;
+import org.apache.taverna.scufl2.api.common.Scufl2Tools;
+import org.apache.taverna.scufl2.api.container.WorkflowBundle;
+import org.apache.taverna.scufl2.api.core.Workflow;
+import org.apache.taverna.scufl2.api.profiles.Profile;
+
+
+/**
+ * Utility class for reading and writing <code>WorkflowBundle</code>s.
+ * <p>
+ * This class depends on implemented {@link WorkflowBundleReader} and
+ * {@link WorkflowBundleWriter} instances, which are discovered from the
+ * classpath using {@link ServiceLoader} or set using {@link #setReaders(List)}
+ * and {@link #setWriters(List)}. An OSGi service descriptors is provided for
+ * instantiating this class as bean <code>workflowBundleIO</code>, while
+ * non-OSGi uses can just instantiate this class where needed.
+ * <p>
+ * The methods {@link #readBundle(File, String)},
+ * {@link #readBundle(InputStream, String)}, {@link #readBundle(URL, String)}
+ * and {@link #writeBundle(WorkflowBundle, File, String)}/
+ * {@link #writeBundle(WorkflowBundle, OutputStream, String)} take an argument
+ * to indicate the media type of the format. The reader methods from file and
+ * URL allow the parameter to be <code>null</code> in order to guess the format,
+ * but the writer method requires the format to be specified explicitly.
+ * <p>
+ * Known supported formats (as of 2013-04-23):
+ * <dl>
+ * <dt>text/vnd.taverna.scufl2.structure</dt>
+ * <dd>A textual tree-view, useful for debugging, but probably incomplete for
+ * actual workflow execution. Reader and writer provided by scufl2-api (this
+ * module).</dd>
+ * <dt>application/vnd.taverna.scufl2.workflow-bundle</dt>
+ * <dd>The <a href=
+ * 'http://dev.mygrid.org.uk/wiki/display/developer/Taverna+Workflow+Bundle'>SCU
+ * F L 2 workflow bundle</a> format, a ZIP container of RDF/XML files. Reader
+ * and writer provided by the scufl2-rdfxml module.</dd>
+ * <dt>application/vnd.taverna.t2flow+xml</dt>
+ * <dd>The Taverna 2 workflow format t2flow. An XML format based on XMLBeans
+ * serialization of T2 java objects. Reader provided by the scufl2-t2flow
+ * module.
+ * <dt>application/vnd.taverna.scufl+xml</dt>
+ * <dd>The Taverna 1 workflow format SCUFL. An XML format made for the FreeFluo
+ * workflow engine. Experimental reader provided by the scufl2-scufl module.
+ * <dt>text/vnd.wf4ever.wfdesc+turtle</dt>
+ * <dd>An abstract workflow structure format by the <a
+ * href="http://www.wf4ever-project.org/">Wf4Ever project. RDF Turtle according
+ * to the <a href="http://purl.org/wf4ever/model">wfdesc ontology</a>. Writer
+ * provided by the third-party <a
+ * href="https://github.com/wf4ever/scufl2-wfdesc">scufl2-wfdesc</a> module.
+ * </dl>
+ */
+public class WorkflowBundleIO {
+ private static Logger log = Logger.getLogger(WorkflowBundleIO.class
+ .getCanonicalName());
+
+ private static final Scufl2Tools scufl2Tools = new Scufl2Tools();
+ // delay initialising the ServiceLoaders
+ protected ServiceLoader<WorkflowBundleWriter> writersLoader;
+ protected ServiceLoader<WorkflowBundleReader> readersLoader;
+
+ private List<WorkflowBundleWriter> writers;
+
+ private List<WorkflowBundleReader> readers;
+
+ protected List<WorkflowBundleReader> discoverReaders() {
+ synchronized (this) {
+ if (readersLoader == null)
+ /*
+ * FIXME: This uses Thread.currentThread.getContextClassLoader()
+ * - depending on who gets synchronized-block first this can
+ * vary - but as it's still local per instance of
+ * WorkflowBundleIO it should be OK for now.
+ */
+ readersLoader = ServiceLoader.load(WorkflowBundleReader.class);
+ }
+
+ List<WorkflowBundleReader> allReaders = new ArrayList<>();
+ for (WorkflowBundleReader reader : readersLoader)
+ allReaders.add(reader);
+ return allReaders;
+ }
+
+ protected List<WorkflowBundleWriter> discoverWriters() {
+ synchronized (this) {
+ if (writersLoader == null)
+ /*
+ * FIXME: This uses Thread.currentThread.getContextClassLoader()
+ * - depending on who gets synchronized-block first this can
+ * vary - but as it's still local per instance of
+ * WorkflowBundleIO it should be OK for now.
+ */
+ writersLoader = ServiceLoader.load(WorkflowBundleWriter.class);
+ }
+ List<WorkflowBundleWriter> allWriters = new ArrayList<>();
+ for (WorkflowBundleWriter writer : writersLoader)
+ allWriters.add(writer);
+ return allWriters;
+ }
+
+ /**
+ * Returns a <code>WorkflowBundleReader</code> for the specified media type.
+ *
+ * If there is more than one <code>WorkflowBundleReader</code> for the
+ * specified media type the first reader discovered is returned. Subsequent
+ * calls to this method may return a different reader.
+ *
+ * If there is no <code>WorkflowBundleReader</code> for the specified media
+ * type <code>null</code> is returned.
+ *
+ * @param mediaType
+ * the media type of the <code>WorkflowBundleReader</code>
+ * @return a <code>WorkflowBundleReader</code> for the specified media type
+ */
+ public WorkflowBundleReader getReaderForMediaType(String mediaType) {
+ for (WorkflowBundleReader reader : getReaders())
+ if (reader.getMediaTypes().contains(mediaType))
+ return reader;
+ return null;
+ }
+
+ /**
+ * Returns all the available <code>WorkflowBundleReader</code>s.
+ *
+ * @return all the available <code>WorkflowBundleReader</code>s
+ */
+ public List<WorkflowBundleReader> getReaders() {
+ if (readers == null)
+ return discoverReaders();
+ return readers;
+ }
+
+ /**
+ * Get the supported media types for reading.
+ * <p>
+ * Returned media types can be used with {@link #readBundle(File, String)},
+ * {@link #readBundle(InputStream, String)} and/or
+ * {@link #readBundle(URL, String)}.
+ *
+ * @return A (usually sorted) set of media types
+ */
+ public Set<String> getSupportedReaderMediaTypes() {
+ Set<String> mediaTypes = new TreeSet<>();
+ for (WorkflowBundleReader reader : getReaders())
+ mediaTypes.addAll(reader.getMediaTypes());
+ return mediaTypes;
+ }
+
+ /**
+ * Get the supported media types for writing.
+ * <p>
+ * Returned media types can be used with
+ * {@link #writeBundle(WorkflowBundle, File, String)} and/or
+ * {@link #writeBundle(WorkflowBundle, OutputStream, String)}.
+ *
+ * @return A (usually sorted) set of media types
+ */
+ public Set<String> getSupportedWriterMediaTypes() {
+ Set<String> mediaTypes = new TreeSet<>();
+ for (WorkflowBundleWriter writer : getWriters())
+ mediaTypes.addAll(writer.getMediaTypes());
+ return mediaTypes;
+ }
+
+ /**
+ * Returns a <code>WorkflowBundleWriter</code> for the specified media type.
+ *
+ * If there is more than one <code>WorkflowBundleWriter</code> for the
+ * specified media type the first writer discovered is returned. Subsequent
+ * calls to this method may return a different writer.
+ *
+ * If there is no <code>WorkflowBundleWriter</code> for the specified media
+ * type <code>null</code> is returned.
+ *
+ * @param mediaType
+ * the media type of the <code>WorkflowBundleWriter</code>
+ * @return a <code>WorkflowBundleWriter</code> for the specified media type
+ */
+ public WorkflowBundleWriter getWriterForMediaType(String mediaType) {
+ for (WorkflowBundleWriter writer : getWriters())
+ if (writer.getMediaTypes().contains(mediaType))
+ return writer;
+ return null;
+ }
+
+ /**
+ * Returns all the available <code>WorkflowBundleWriter</code>s.
+ *
+ * @return all the available <code>WorkflowBundleWriter</code>s
+ */
+ public List<WorkflowBundleWriter> getWriters() {
+ if (writers == null)
+ return discoverWriters();
+ return writers;
+ }
+
+ /**
+ * Attempt to guess the media type for a stream or file that starts with
+ * these bytes.
+ * <p>
+ * All registered {@link #getReaders()} are consulted.
+ * <p>
+ * Return <code>null</code> if ambiguous (more than one possibility) or
+ * unknown.
+ *
+ * @param firstBytes
+ * The initial bytes, at least 512 bytes long unless the resource
+ * is smaller.
+ * @return The recognised media type, or <code>null</code> if the bytes were
+ * ambiguous or unknown.
+ */
+ public String guessMediaTypeForSignature(byte[] firstBytes) {
+ Set<String> mediaTypes = new HashSet<>();
+ for (WorkflowBundleReader reader : getReaders()) {
+ String guess = reader.guessMediaTypeForSignature(firstBytes);
+ if (guess != null)
+ mediaTypes.add(guess);
+ }
+ if (mediaTypes.isEmpty())
+ return null;
+ if (mediaTypes.size() > 1) {
+ log.warning("Multiple media types found: " + mediaTypes);
+ return null;
+ }
+ return mediaTypes.iterator().next();
+ }
+
+ /**
+ * Reads a file containing a workflow bundle in the specified media type and
+ * returns a <code>WorkflowBundle</code>.
+ *
+ * @param bundleFile
+ * the file containing the workflow bundle
+ * @param mediaType
+ * the media type of the workflow bundle. A
+ * <code>WorkflowBundleReader</code> must exist for this media
+ * type. If <code>null</code>, the media type will be guessed as
+ * with {@link #guessMediaTypeForSignature(byte[])}.
+ * @return the <code>WorkflowBundle</code> read from the file
+ * @throws ReaderException
+ * if there is an error parsing the workflow bundle
+ * @throws IOException
+ * if there is an error reading the file
+ * @throws IllegalArgumentException
+ * if a <code>WorkflowBundleReader</code> cannot be found for
+ * the media type
+ */
+ public WorkflowBundle readBundle(File bundleFile, String mediaType)
+ throws ReaderException, IOException {
+ if (mediaType == null) {
+ byte[] firstBytes = new byte[1024];
+ try (FileInputStream fileIn = new FileInputStream(bundleFile)) {
+ fileIn.read(firstBytes);
+ }
+ mediaType = guessMediaTypeForSignature(firstBytes);
+ }
+ WorkflowBundleReader reader = getReaderForMediaType(mediaType);
+ if (reader == null) {
+ if (mediaType == null)
+ throw new IllegalArgumentException(
+ "Could not guess media type for " + bundleFile);
+ throw new IllegalArgumentException(
+ "Could not find reader for media type " + mediaType);
+ }
+ return reader.readBundle(bundleFile, mediaType);
+ }
+
+ /**
+ * Reads a stream containing a workflow bundle in the specified media type
+ * and returns a <code>WorkflowBundle</code>.
+ *
+ * @param inputStream
+ * the stream containing the workflow bundle
+ * @param mediaType
+ * the media type of the workflow bundle. A
+ * <code>WorkflowBundleReader</code> must exist for this media
+ * type. If <code>null</code>, the media type will be guessed as
+ * with {@link #guessMediaTypeForSignature(byte[])}.
+ * @return the <code>WorkflowBundle</code> read from the stream
+ * @throws ReaderException
+ * if there is an error parsing the workflow bundle
+ * @throws IOException
+ * if there is an error reading from the stream
+ * @throws IllegalArgumentException
+ * if a <code>WorkflowBundleReader</code> cannot be found for
+ * the media type
+ */
+ public WorkflowBundle readBundle(InputStream inputStream, String mediaType)
+ throws ReaderException, IOException {
+ if (mediaType == null) {
+ byte[] firstBytes = new byte[1024];
+ inputStream = new BufferedInputStream(inputStream);
+ try {
+ inputStream.mark(firstBytes.length * 2);
+ inputStream.read(firstBytes);
+ mediaType = guessMediaTypeForSignature(firstBytes);
+ } finally {
+ inputStream.reset();
+ // Important = so readBundle can start from the beginning
+ }
+ }
+ WorkflowBundleReader reader = getReaderForMediaType(mediaType);
+ if (reader == null) {
+ if (mediaType == null)
+ throw new IllegalArgumentException(
+ "Could not guess media type for input stream");
+ throw new IllegalArgumentException(
+ "Could not find reader for media type " + mediaType);
+ }
+ return reader.readBundle(inputStream, mediaType);
+ }
+
+ /**
+ * Reads a URL containing a workflow bundle in the specified media type and
+ * returns a <code>WorkflowBundle</code>.
+ *
+ * @param url
+ * the URL containing the workflow bundle
+ * @param mediaType
+ * the media type of the workflow bundle. A
+ * <code>WorkflowBundleReader</code> must exist for this media
+ * type If <code>null</code>, the media type will be guessed as
+ * with {@link #guessMediaTypeForSignature(byte[])}.
+ * @return the <code>WorkflowBundle</code> read from the URL
+ * @throws ReaderException
+ * if there is an error parsing the workflow bundle
+ * @throws IOException
+ * if there is an error reading from the stream
+ * @throws IllegalArgumentException
+ * if a <code>WorkflowBundleReader</code> cannot be found for
+ * the media type
+ */
+ public WorkflowBundle readBundle(URL url, String mediaType)
+ throws ReaderException, IOException {
+ URLConnection connection = url.openConnection();
+ if (mediaType != null && !mediaType.isEmpty())
+ addAcceptHeaders(mediaType, connection);
+ else
+ for (String supportedType : getSupportedReaderMediaTypes()) {
+ addAcceptHeaders(supportedType, connection);
+ connection.addRequestProperty("Accept", "*/*;q=0.1");
+ }
+
+ try (InputStream inputStream = connection.getInputStream()) {
+ String contentType = connection.getContentType();
+ List<String> ignoreTypes = Arrays.asList("text/plain",
+ "application/octet-stream", "application/zip",
+ "application/x-zip-compressed", "text/xml",
+ "application/xml", "content/unknown");
+ if (contentType == null || contentType.isEmpty())
+ contentType = mediaType; // might still be null -> guess
+ else
+ for (String ignore : ignoreTypes)
+ if (contentType.toLowerCase().startsWith(ignore))
+ contentType = mediaType; // might still be null -> guess
+ // TODO: Pass URL to reader (as baseURI)
+ return readBundle(url.openStream(), contentType);
+ }
+ }
+
+ private void addAcceptHeaders(String mediaType, URLConnection connection) {
+ connection.addRequestProperty("Accept", mediaType);
+
+ if (mediaType.endsWith("+zip")
+ || mediaType.equals("vnd.taverna.scufl2.workflow-bundle")) {
+ connection.addRequestProperty("Accept", "application/zip;q=0.5");
+ connection.addRequestProperty("Accept",
+ "application/x-zip-compressed;q=0.5");
+ } else if (mediaType.endsWith("+xml")) {
+ connection.setRequestProperty("Accept", "application/xml;q=0.6");
+ connection.setRequestProperty("Accept", "text/xml;q=0.5");
+ }
+ }
+
+ /**
+ * Sets the <code>WorkflowBundleReader</code>s.
+ * <p>
+ * This method will normally be called by Spring when wiring beans.
+ *
+ * @param readers
+ * the <code>WorkflowBundleReader</code>s
+ */
+ public void setReaders(List<WorkflowBundleReader> readers) {
+ this.readers = readers;
+ }
+
+ /**
+ * Sets the <code>WorkflowBundleWriter</code>s.
+ * <p>
+ * This method will normally be called by Spring when wiring beans.
+ *
+ * @param readers
+ * the <code>WorkflowBundleWriter</code>s
+ */
+ public void setWriters(List<WorkflowBundleWriter> writers) {
+ this.writers = writers;
+ }
+
+ /**
+ * Write a <code>WorkflowBundle</code> to a file with specified media type.
+ * <p>
+ * {@link Scufl2Tools#setParents(WorkflowBundle)} will be called on the
+ * bundle to ensure everything contained by the bundle has it as an
+ * ancestor.
+ *
+ * @param wfBundle
+ * the workflow bundle to write
+ * @param destination
+ * the file to write the workflow bundle to
+ * @param mediaType
+ * the media type to write workflow bundle in. A
+ * <code>WorkflowBundleWriter</code> must exist for this media
+ * type
+ * @throws WriterException
+ * if there is an error writing the workflow bundle
+ * @throws IOException
+ * if there is an error writing the file
+ * @throws IllegalArgumentException
+ * if a <code>WorkflowBundleWriter</code> cannot be found for
+ * the media type
+ */
+ public void writeBundle(WorkflowBundle wfBundle, File destination,
+ String mediaType) throws WriterException, IOException {
+ WorkflowBundleWriter writer = getWriterForMediaType(mediaType);
+ if (writer == null)
+ throw new IllegalArgumentException(
+ "Could not find writer for media type " + mediaType);
+ scufl2Tools.setParents(wfBundle);
+ writer.writeBundle(wfBundle, destination, mediaType);
+ }
+
+ /**
+ * Write a <code>WorkflowBundle</code> to a stream with specified media
+ * type.
+ * <p>
+ * {@link Scufl2Tools#setParents(WorkflowBundle)} will be called on the
+ * bundle to ensure everything contained by the bundle has it as an
+ * ancestor.
+ *
+ * @param wfBundle
+ * the workflow bundle to write
+ * @param output
+ * the stream to write the workflow bundle to
+ * @param mediaType
+ * the media type to write workflow bundle in. A
+ * <code>WorkflowBundleWriter</code> must exist for this media
+ * type
+ * @throws WriterException
+ * if there is an error writing the workflow bundle
+ * @throws IOException
+ * if there is an error writing to the stream
+ * @throws IllegalArgumentException
+ * if a <code>WorkflowBundleWriter</code> cannot be found for
+ * the media type
+ */
+ public void writeBundle(WorkflowBundle wfBundle, OutputStream output,
+ String mediaType) throws WriterException, IOException {
+ WorkflowBundleWriter writer = getWriterForMediaType(mediaType);
+ if (writer == null)
+ throw new IllegalArgumentException(
+ "Could not find writer for media type " + mediaType);
+ scufl2Tools.setParents(wfBundle);
+ writer.writeBundle(wfBundle, output, mediaType);
+ }
+
+ /**
+ * Create a new WorkflowBundle with a default workflow and profile.
+ * <p>
+ * Unlike the {@link WorkflowBundle} constructor, this method will also make
+ * a {@link WorkflowBundle#getMainWorkflow()} and
+ * {@link WorkflowBundle#getMainProfile()}, simplifying construction of
+ * workflow bundles from scratch.
+ * <p>
+ * Each of the bundle, workflow and profile will also have a revision set
+ * using {@link Revisioned#newRevision()} and their names set to short
+ * default values.
+ *
+ * @return A template {@link WorkflowBundle} which has a main workflow and
+ * main profile
+ */
+ public WorkflowBundle createBundle() {
+ WorkflowBundle wb = new WorkflowBundle();
+ wb.setName("bundle1");
+
+ Workflow workflow = new Workflow();
+ workflow.setName("workflow1");
+ workflow.setParent(wb);
+ workflow.newRevision();
+
+ Profile profile = new Profile();
+ profile.setName("profile1");
+ profile.setParent(wb);
+ profile.newRevision();
+
+ wb.setMainWorkflow(workflow);
+ wb.setMainProfile(profile);
+ wb.newRevision();
+ return wb;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/WorkflowBundleReader.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/WorkflowBundleReader.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/WorkflowBundleReader.java
new file mode 100644
index 0000000..889ca43
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/WorkflowBundleReader.java
@@ -0,0 +1,110 @@
+package org.apache.taverna.scufl2.api.io;
+
+/*
+ * 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.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Set;
+
+import org.apache.taverna.scufl2.api.container.WorkflowBundle;
+
+
+/**
+ * A reader for {@link WorkflowBundle}s.
+ *
+ * Implementations specify workflow bundle formats (media types/mime types) that
+ * they can read.
+ */
+public interface WorkflowBundleReader {
+ /**
+ * Return the media types that this reader can read.
+ * <p>
+ * Returned media types are must be valid <code>mediaType</code> arguments
+ * to {@link #readBundle(File, String)} and/or
+ * {@link #readBundle(InputStream, String)} for this reader.
+ * <p>
+ * It is recommended, but not required, that the reader can also recognise
+ * those media types from {@link #guessMediaTypeForSignature(byte[])}.
+ * <p>
+ * If the returned set is empty, the reader should should be able to
+ * Recognise at least one media type from
+ * {@link #guessMediaTypeForSignature(byte[])}.
+ *
+ * @return the media types that this reader can read, or an empty set if
+ * this reader can't read any bundle formats.
+ */
+ Set<String> getMediaTypes();
+
+ /**
+ * Read a file containing a workflow bundle in the specified media type and
+ * return a <code>WorkflowBundle</code>.
+ *
+ * @param bundleFile
+ * the file containing the workflow bundle
+ * @param mediaType
+ * the media type of the workflow bundle
+ * @return the <code>WorkflowBundle</code> read from the file
+ * @throws ReaderException
+ * if there is an error parsing the workflow bundle
+ * @throws IOException
+ * if there is an error reading the file
+ */
+ WorkflowBundle readBundle(File bundleFile, String mediaType)
+ throws ReaderException, IOException;
+
+ /**
+ * Read a stream containing a workflow bundle in the specified media type
+ * and return a <code>WorkflowBundle</code>.
+ *
+ * @param inputStream
+ * the stream containing the workflow bundle
+ * @param mediaType
+ * the media type of the workflow bundle
+ * @return the <code>WorkflowBundle</code> read from the stream
+ * @throws ReaderException
+ * if there is an error parsing the workflow bundle
+ * @throws IOException
+ * if there is an error reading from the stream
+ */
+ WorkflowBundle readBundle(InputStream inputStream, String mediaType)
+ throws ReaderException, IOException;
+
+ /**
+ * Attempt to guess the media type for a stream or file that starts with
+ * these bytes.
+ * <p>
+ * Return <code>null</code> if ambiguous (more than one possibility) or
+ * unknown.
+ * <p>
+ * Typically a WorkflowBundleReader should be able to recognise the same
+ * types as those listed in {@link #getMediaTypes()}, but this is no
+ * requirement. A WorkflowBundleReader could also recognise types not listed
+ * in its {@link #getMediaTypes()}.
+ *
+ * @param firstBytes
+ * The initial bytes, at least 512 bytes long unless the resource
+ * is smaller.
+ * @return The recognised media type, or <code>null</code> if the bytes were
+ * ambiguous or unknown.
+ */
+ String guessMediaTypeForSignature(byte[] firstBytes);
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/WorkflowBundleWriter.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/WorkflowBundleWriter.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/WorkflowBundleWriter.java
new file mode 100644
index 0000000..a74127f
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/WorkflowBundleWriter.java
@@ -0,0 +1,79 @@
+package org.apache.taverna.scufl2.api.io;
+
+/*
+ * 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.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Set;
+
+import org.apache.taverna.scufl2.api.container.WorkflowBundle;
+
+
+/**
+ * A writer for {@link WorkflowBundle}s.
+ *
+ * Implementations specify workflow bundle formats (media types) that they can
+ * write.
+ */
+public interface WorkflowBundleWriter {
+ /**
+ * Returns the media types that this writer can handle.
+ *
+ * @return the media types that this writer can handle
+ */
+ Set<String> getMediaTypes();
+
+ /**
+ * Writes a <code>WorkflowBundle</code> to a file with specified media type.
+ *
+ * @param wfBundle
+ * the workflow bundle to write
+ * @param destination
+ * the file to write the workflow bundle to
+ * @param mediaType
+ * the media type to write workflow bundle in
+ * @throws WriterException
+ * if there is an error writing the workflow bundle
+ * @throws IOException
+ * if there is an error writing the file
+ */
+ void writeBundle(WorkflowBundle wfBundle, File destination, String mediaType)
+ throws WriterException, IOException;
+
+ /**
+ * Writes a <code>WorkflowBundle</code> to a stream with specified media
+ * type.
+ *
+ * @param wfBundle
+ * the workflow bundle to write
+ * @param output
+ * the stream to write the workflow bundle to
+ * @param mediaType
+ * the media type to write workflow bundle in
+ * @throws WriterException
+ * if there is an error writing the workflow bundle
+ * @throws IOException
+ * if there is an error writing to the stream
+ */
+ void writeBundle(WorkflowBundle wfBundle, OutputStream output,
+ String mediaType) throws WriterException, IOException;
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/WriterException.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/WriterException.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/WriterException.java
new file mode 100644
index 0000000..f316a43
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/WriterException.java
@@ -0,0 +1,76 @@
+package org.apache.taverna.scufl2.api.io;
+
+/*
+ * 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.container.WorkflowBundle;
+
+/**
+ * Thrown when there is a problem writing a {@link WorkflowBundle}
+ *
+ * @see WorkflowBundleIO#writeBundle(WorkflowBundle, java.io.File, String)
+ * @see WorkflowBundleIO#writeBundle(WorkflowBundle, java.io.OutputStream,
+ * String)
+ * @see WorkflowBundleWriter#writeBundle(WorkflowBundle, java.io.File, String)
+ * @see WorkflowBundleWriter#writeBundle(WorkflowBundle, java.io.OutputStream,
+ * String)
+ */
+@SuppressWarnings("serial")
+public class WriterException extends Exception {
+
+ /**
+ * Constructs an exception with no message or cause.
+ */
+ public WriterException() {
+ }
+
+ /**
+ * Constructs an exception with the specified message and no cause.
+ *
+ * @param message
+ * details about the exception. Can be <code>null</code>
+ */
+ public WriterException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs an exception with the specified message and cause.
+ *
+ * @param message
+ * details about the exception. Can be <code>null</code>
+ * @param cause
+ * the cause of the exception. Can be <code>null</code>
+ */
+ public WriterException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Constructs an exception with the specified cause and and the same message
+ * as the cause (if the cause is not null).
+ *
+ * @param cause
+ * the cause of the exception. Can be <code>null</code>
+ */
+ public WriterException(Throwable cause) {
+ super(cause);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/structure/StructureReader.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/structure/StructureReader.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/structure/StructureReader.java
new file mode 100644
index 0000000..c19444b
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/structure/StructureReader.java
@@ -0,0 +1,452 @@
+package org.apache.taverna.scufl2.api.io.structure;
+
+/*
+ * 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 static java.lang.System.arraycopy;
+import static java.util.Collections.singleton;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.util.Scanner;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.taverna.scufl2.api.activity.Activity;
+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.DataLink;
+import org.apache.taverna.scufl2.api.core.Processor;
+import org.apache.taverna.scufl2.api.core.Workflow;
+import org.apache.taverna.scufl2.api.io.ReaderException;
+import org.apache.taverna.scufl2.api.io.WorkflowBundleReader;
+import org.apache.taverna.scufl2.api.port.InputActivityPort;
+import org.apache.taverna.scufl2.api.port.InputProcessorPort;
+import org.apache.taverna.scufl2.api.port.InputWorkflowPort;
+import org.apache.taverna.scufl2.api.port.OutputActivityPort;
+import org.apache.taverna.scufl2.api.port.OutputProcessorPort;
+import org.apache.taverna.scufl2.api.port.OutputWorkflowPort;
+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.Profile;
+
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * A <code>WorkflowBundleReader</code> that reads a {@link WorkflowBundle} in
+ * Scufl2 Structure format.
+ */
+public class StructureReader implements WorkflowBundleReader {
+ public enum Level {
+ WorkflowBundle, Workflow, Processor, Activity, Links, Profile, Configuration, ProcessorBinding, OutputPortBindings, InputPortBindings, JSON, Controls
+ }
+
+ private static final String ACTIVITY_SLASH = "activity/";
+ public static final String TEXT_VND_TAVERNA_SCUFL2_STRUCTURE = "text/vnd.taverna.scufl2.structure";
+
+ private WorkflowBundle wb;
+ private Level level;
+ private String mainWorkflow;
+ private Workflow workflow;
+ private Processor processor;
+ private Activity activity;
+ Pattern linkPattern = Pattern
+ .compile("'(.*[^\\\\])'\\s->\\s'(.*[^\\\\\\\\])'");
+ Pattern blockPattern = Pattern
+ .compile("\\s*block\\s+'(.*[^\\\\])'\\s+until\\s+'(.*[^\\\\\\\\])'\\s+finish");
+ private String mainProfile;
+ private Profile profile;
+ private Configuration configuration;
+ private ProcessorBinding processorBinding;
+ protected Scanner scanner;
+
+ @Override
+ public Set<String> getMediaTypes() {
+ return singleton(TEXT_VND_TAVERNA_SCUFL2_STRUCTURE);
+ }
+
+ // synchronized because we share wb/scanner fields across the instance
+ protected synchronized WorkflowBundle parse(InputStream is)
+ throws ReaderException {
+ scanner = new Scanner(is);
+ try {
+ wb = new WorkflowBundle();
+ while (scanner.hasNextLine())
+ parseLine(scanner.nextLine());
+ return wb;
+ } finally {
+ scanner.close();
+ }
+ }
+
+ protected void parseLine(final String nextLine) throws ReaderException {
+ try (Scanner scanner = new Scanner(nextLine.trim())) {
+ // In case it is the last line
+ if (!scanner.hasNext())
+ return;
+ // allow any whitespace
+ String next = scanner.next();
+
+ if (next.isEmpty())
+ return;
+ switch (next) {
+ case "WorkflowBundle":
+ parseWorkflowBundle(scanner);
+ return;
+ case "MainWorkflow":
+ mainWorkflow = parseName(scanner);
+ return;
+ case "Workflow":
+ parseWorkflow(scanner);
+ return;
+ case "In":
+ case "Out":
+ parsePort(scanner, next);
+ return;
+ case "Links":
+ level = Level.Links;
+ processor = null;
+ return;
+ case "Controls":
+ level = Level.Controls;
+ return;
+ case "MainProfile":
+ mainProfile = parseName(scanner);
+ return;
+ case "Profile":
+ parseProfile(scanner);
+ return;
+ case "Type":
+ parseType(nextLine);
+ return;
+ case "ProcessorBinding":
+ parseProcessorBinding(scanner);
+ return;
+ case "InputPortBindings":
+ level = Level.InputPortBindings;
+ return;
+ case "OutputPortBindings":
+ level = Level.OutputPortBindings;
+ return;
+ case "Configuration":
+ parseConfiguration(scanner);
+ return;
+ case "Configures":
+ parseConfigures(scanner);
+ return;
+ case "Activity":
+ switch (level) {
+ case Profile:
+ case Activity:
+ level = Level.Activity;
+ activity = new Activity();
+ activity.setName(parseName(scanner));
+ profile.getActivities().add(activity);
+ return;
+ case ProcessorBinding:
+ Activity boundActivity = profile.getActivities().getByName(
+ parseName(scanner));
+ processorBinding.setBoundActivity(boundActivity);
+ return;
+ default:
+ break;
+ }
+ break;
+ case "Processor":
+ switch (level) {
+ case Workflow:
+ case Processor:
+ level = Level.Processor;
+ processor = new Processor();
+ processor.setName(parseName(scanner));
+ processor.setParent(workflow);
+ workflow.getProcessors().add(processor);
+ return;
+ case ProcessorBinding:
+ String[] wfProcName = parseName(scanner).split(":");
+ Workflow wf = wb.getWorkflows().getByName(wfProcName[0]);
+ Processor boundProcessor = wf.getProcessors().getByName(
+ wfProcName[1]);
+ processorBinding.setBoundProcessor(boundProcessor);
+ return;
+ default:
+ break;
+ }
+ break;
+ }
+
+ if (next.equals("block")) {
+ Matcher blockMatcher = blockPattern.matcher(nextLine);
+ blockMatcher.find();
+ String block = blockMatcher.group(1);
+ String untilFinish = blockMatcher.group(2);
+
+ Processor blockProc = workflow.getProcessors().getByName(block);
+ Processor untilFinishedProc = workflow.getProcessors()
+ .getByName(untilFinish);
+ new BlockingControlLink(blockProc, untilFinishedProc);
+ }
+ if (next.startsWith("'") && level.equals(Level.Links)) {
+ Matcher linkMatcher = linkPattern.matcher(nextLine);
+ linkMatcher.find();
+ String firstLink = linkMatcher.group(1);
+ String secondLink = linkMatcher.group(2);
+
+ SenderPort senderPort;
+ if (firstLink.contains(":")) {
+ String[] procPort = firstLink.split(":");
+ Processor proc = workflow.getProcessors().getByName(
+ procPort[0]);
+ senderPort = proc.getOutputPorts().getByName(procPort[1]);
+ } else
+ senderPort = workflow.getInputPorts().getByName(firstLink);
+
+ ReceiverPort receiverPort;
+ if (secondLink.contains(":")) {
+ String[] procPort = secondLink.split(":");
+ Processor proc = workflow.getProcessors().getByName(
+ procPort[0]);
+ receiverPort = proc.getInputPorts().getByName(procPort[1]);
+ } else
+ receiverPort = workflow.getOutputPorts().getByName(
+ secondLink);
+
+ new DataLink(workflow, senderPort, receiverPort);
+ return;
+ }
+
+ if (next.startsWith("'")
+ && (level == Level.InputPortBindings || level == Level.OutputPortBindings)) {
+ Matcher linkMatcher = linkPattern.matcher(nextLine);
+ linkMatcher.find();
+ String firstLink = linkMatcher.group(1);
+ String secondLink = linkMatcher.group(2);
+ if (level == Level.InputPortBindings) {
+ InputProcessorPort processorPort = processorBinding
+ .getBoundProcessor().getInputPorts()
+ .getByName(firstLink);
+ InputActivityPort activityPort = processorBinding
+ .getBoundActivity().getInputPorts()
+ .getByName(secondLink);
+ new ProcessorInputPortBinding(processorBinding,
+ processorPort, activityPort);
+ } else {
+ OutputActivityPort activityPort = processorBinding
+ .getBoundActivity().getOutputPorts()
+ .getByName(firstLink);
+ OutputProcessorPort processorPort = processorBinding
+ .getBoundProcessor().getOutputPorts()
+ .getByName(secondLink);
+ new ProcessorOutputPortBinding(processorBinding,
+ activityPort, processorPort);
+ }
+ return;
+ }
+ if (level == Level.JSON) {
+ /*
+ * A silly reader that feeds (no more than) a single line at a
+ * time from our parent scanner, starting with the current line
+ */
+ Reader reader = new Reader() {
+ char[] line = nextLine.toCharArray();
+ int pos = 0;
+
+ @Override
+ public int read(char[] cbuf, int off, int len)
+ throws IOException {
+ if (pos >= line.length) {
+ // Need to read next line to fill buffer
+ if (!StructureReader.this.scanner.hasNextLine())
+ return -1;
+ String newLine = StructureReader.this.scanner
+ .nextLine();
+ pos = 0;
+ line = newLine.toCharArray();
+ // System.out.println("Read new line: " + newLine);
+ }
+ int length = Math.min(len, line.length - pos);
+ if (length <= 0)
+ return 0;
+ arraycopy(line, pos, cbuf, off, length);
+ pos += length;
+ return length;
+ }
+
+ @Override
+ public void close() throws IOException {
+ line = null;
+ }
+ };
+
+ ObjectMapper mapper = new ObjectMapper();
+ try {
+ JsonParser parser = mapper.getFactory()
+ .createParser(reader);
+ JsonNode jsonNode = parser.readValueAs(JsonNode.class);
+ // System.out.println("Parsed " + jsonNode);
+ configuration.setJson(jsonNode);
+ } catch (IOException e) {
+ throw new ReaderException("Can't parse json", e);
+ }
+ level = Level.Configuration;
+ return;
+ }
+ }
+ }
+
+ private void parseWorkflowBundle(Scanner scanner) {
+ level = Level.WorkflowBundle;
+ String name = parseName(scanner);
+ wb.setName(name);
+ }
+
+ private void parseWorkflow(Scanner scanner) {
+ level = Level.Workflow;
+ workflow = new Workflow();
+ String workflowName = parseName(scanner);
+ workflow.setName(workflowName);
+ wb.getWorkflows().add(workflow);
+ if (workflowName.equals(mainWorkflow))
+ wb.setMainWorkflow(workflow);
+ }
+
+ private void parsePort(Scanner scanner, String next) throws ReaderException {
+ boolean in = next.equals("In");
+ String portName = parseName(scanner);
+ switch (level) {
+ case Workflow:
+ if (in)
+ new InputWorkflowPort(workflow, portName);
+ else
+ new OutputWorkflowPort(workflow, portName);
+ break;
+ case Processor:
+ if (in)
+ new InputProcessorPort(processor, portName);
+ else
+ new OutputProcessorPort(processor, portName);
+ break;
+ case Activity:
+ if (in)
+ new InputActivityPort(activity, portName);
+ else
+ new OutputActivityPort(activity, portName);
+ break;
+ default:
+ throw new ReaderException("Unexpected " + next + " at level "
+ + level);
+ }
+ }
+
+ private void parseProfile(Scanner scanner) {
+ level = Level.Profile;
+ profile = new Profile();
+ String profileName = parseName(scanner);
+ profile.setName(profileName);
+ wb.getProfiles().add(profile);
+ if (profileName.equals(mainProfile))
+ wb.setMainProfile(profile);
+ }
+
+ private void parseType(String nextLine) {
+ URI uri = URI.create(nextLine.split("[<>]")[1]);
+ switch (level) {
+ case Activity:
+ activity.setType(uri);
+ break;
+ case Configuration:
+ configuration.setType(uri);
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void parseProcessorBinding(Scanner scanner) {
+ level = Level.ProcessorBinding;
+ processorBinding = new ProcessorBinding();
+ String bindingName = parseName(scanner);
+ processorBinding.setName(bindingName);
+ profile.getProcessorBindings().add(processorBinding);
+ }
+
+ private void parseConfiguration(Scanner scanner) {
+ level = Level.Configuration;
+ configuration = new Configuration();
+ String configName = parseName(scanner);
+ configuration.setName(configName);
+ profile.getConfigurations().add(configuration);
+ }
+
+ private void parseConfigures(Scanner scanner) {
+ String configures = parseName(scanner);
+ if (!configures.startsWith(ACTIVITY_SLASH))
+ throw new UnsupportedOperationException("Unknown Configures "
+ + configures);
+ activity = profile.getActivities().getByName(
+ configures.substring(ACTIVITY_SLASH.length(),
+ configures.length()));
+ configuration.setConfigures(activity);
+ level = Level.JSON;
+ }
+
+
+ private String parseName(Scanner scanner) {
+ String name = scanner.findInLine("'(.*[^\\\\])'");
+ return name.substring(1, name.length() - 1);
+ }
+
+ @Override
+ public WorkflowBundle readBundle(File bundleFile, String mediaType)
+ throws IOException, ReaderException {
+ try (BufferedInputStream is = new BufferedInputStream(
+ new FileInputStream(bundleFile))) {
+ return parse(is);
+ }
+ }
+
+ @Override
+ public WorkflowBundle readBundle(InputStream inputStream, String mediaType)
+ throws IOException, ReaderException {
+ return parse(inputStream);
+ }
+
+ @Override
+ public String guessMediaTypeForSignature(byte[] firstBytes) {
+ if (new String(firstBytes, Charset.forName("ISO-8859-1"))
+ .contains("WorkflowBundle '"))
+ return TEXT_VND_TAVERNA_SCUFL2_STRUCTURE;
+ return 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/io/structure/StructureWriter.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/structure/StructureWriter.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/structure/StructureWriter.java
new file mode 100644
index 0000000..50610b0
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/io/structure/StructureWriter.java
@@ -0,0 +1,314 @@
+package org.apache.taverna.scufl2.api.io.structure;
+
+/*
+ * 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 static org.apache.taverna.scufl2.api.io.structure.StructureReader.TEXT_VND_TAVERNA_SCUFL2_STRUCTURE;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.taverna.scufl2.api.activity.Activity;
+import org.apache.taverna.scufl2.api.common.Named;
+import org.apache.taverna.scufl2.api.common.NamedSet;
+import org.apache.taverna.scufl2.api.common.Ported;
+import org.apache.taverna.scufl2.api.common.URITools;
+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.io.WorkflowBundleWriter;
+import org.apache.taverna.scufl2.api.port.Port;
+import org.apache.taverna.scufl2.api.port.ProcessorPort;
+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.Profile;
+
+
+/**
+ * A <code>WorkflowBundleWriter</code> that writes a {@link WorkflowBundle} in Scufl2 Structure
+ * format.
+ */
+public class StructureWriter implements WorkflowBundleWriter {
+ private StringBuilder sb;
+ private URITools uriTools = new URITools();
+
+ private void append(Named named) {
+ append(" '");
+ append(escapeName(named.getName()));
+ append("'");
+ }
+
+ private void append(String string) {
+ sb.append(string);
+ }
+
+ private void append(URI uri) {
+ sb.append(" <");
+ sb.append(uri.toASCIIString());
+ sb.append(">");
+ }
+
+ private void appendPorts(int indent, Ported wf) {
+ for (Port in : sorted(wf.getInputPorts())) {
+ newLine(indent);
+ append("In");
+ append(in);
+ }
+ for (Port out : sorted(wf.getOutputPorts())) {
+ newLine(indent);
+ append("Out");
+ append(out);
+ }
+ }
+
+ /**
+ * Create a string representation of a workflow bundle. Note that this
+ * method is not thread-safe; call only via the public methods which have
+ * the right locking.
+ */
+ @SuppressWarnings("unchecked")
+ protected String bundleString(WorkflowBundle wb) {
+ sb = new StringBuilder();
+ append("WorkflowBundle");
+ append(wb);
+
+ if (wb.getMainWorkflow() != null) {
+ newLine(1);
+ append("MainWorkflow");
+ append(wb.getMainWorkflow());
+ }
+ for (Workflow wf : sorted(wb.getWorkflows())) {
+ newLine(1);
+ append("Workflow");
+ append(wf);
+ appendPorts(2, wf);
+ for (Processor p : sorted(wf.getProcessors())) {
+ newLine(2);
+ append("Processor");
+ append(p);
+ appendPorts(3, p);
+ }
+
+ // LINKS
+ // We'll need to sort them afterwards
+ List<String> links = new ArrayList<>();
+ for (DataLink dl : wf.getDataLinks())
+ links.add(datalink(dl.getReceivesFrom()) + " -> " + datalink(dl.getSendsTo()));
+ Collections.sort(links);
+ if (!links.isEmpty()) {
+ newLine(2);
+ append("Links");
+ }
+ for (String link : links) {
+ newLine(3);
+ append(link);
+ }
+
+ if (!wf.getControlLinks().isEmpty()) {
+ newLine(2);
+ append("Controls");
+ List<ControlLink> controlLinks = new ArrayList<>(wf.getControlLinks());
+ Collections.sort(controlLinks);
+ for (ControlLink controlLink : controlLinks) {
+ if (!(controlLink instanceof BlockingControlLink))
+ // TODO
+ continue;
+ BlockingControlLink blockingControlLink = (BlockingControlLink) controlLink;
+ newLine(3);
+ append("block");
+ append(blockingControlLink.getBlock());
+ append(" until");
+ append(blockingControlLink.getUntilFinished());
+ append(" finish");
+ }
+ }
+ }
+
+ if (wb.getMainProfile() != null) {
+ newLine(1);
+ append("MainProfile");
+ append(wb.getMainProfile());
+ }
+
+ for (Profile p : sorted(wb.getProfiles())) {
+ newLine(1);
+ append("Profile");
+ append(p);
+ for (Activity a : sorted(p.getActivities())) {
+ newLine(2);
+ append("Activity");
+ append(a);
+ newLine(3);
+ append("Type");
+ append(a.getType());
+ appendPorts(3, a);
+ }
+ for (ProcessorBinding pb : p.getProcessorBindings()) {
+ newLine(2);
+ append("ProcessorBinding");
+ append(pb);
+ newLine(3);
+ append("Activity");
+ append(pb.getBoundActivity());
+ newLine(3);
+ append("Processor");
+ String name = " '" + escapeName(pb.getBoundProcessor().getParent().getName());
+ name = name + ":" + escapeName(pb.getBoundProcessor().getName()) + "'";
+ append(name);
+
+ List<String> links = new ArrayList<String>();
+ for (ProcessorInputPortBinding ip : pb.getInputPortBindings())
+ links.add("'" + escapeName(ip.getBoundProcessorPort().getName()) + "' -> '"
+ + escapeName(ip.getBoundActivityPort().getName()) + "'");
+ Collections.sort(links);
+ if (!links.isEmpty()) {
+ newLine(3);
+ append("InputPortBindings");
+ }
+ for (String link : links) {
+ newLine(4);
+ append(link);
+ }
+
+ links.clear();
+ for (ProcessorOutputPortBinding ip : pb.getOutputPortBindings())
+ // Note: opposite direction as for ProcessorInputPortBinding
+ links.add("'" + escapeName(ip.getBoundActivityPort().getName()) + "' -> '"
+ + escapeName(ip.getBoundProcessorPort().getName()) + "'");
+ Collections.sort(links);
+ if (!links.isEmpty()) {
+ newLine(3);
+ append("OutputPortBindings");
+ }
+ for (String link : links) {
+ newLine(4);
+ append(link);
+ }
+ }
+
+ for (Configuration config : p.getConfigurations()) {
+ newLine(2);
+ append("Configuration");
+ append(config);
+
+ newLine(3);
+ if (config.getType() != null) {
+ append("Type");
+ append(config.getType());
+ }
+ newLine(3);
+ append("Configures");
+ if (config.getConfigures() instanceof Named) {
+ Named c = (Named) config.getConfigures();
+ String cName = "'" + escapeName(c.getClass().getSimpleName().toLowerCase());
+ cName = cName + "/" + escapeName(c.getName()) + "'";
+ append(" " + cName);
+ } else {
+ URI configuredURI = uriTools.relativeUriForBean(config.getConfigures(), p);
+ append(" '" + configuredURI.toASCIIString() + "'");
+ }
+
+ newLine(4);
+ append(config.getJson().toString());
+ }
+
+ }
+ append("\n");
+ //System.out.println(sb);
+ return sb.toString();
+ }
+
+ private String datalink(Port port) {
+ StringBuilder s = new StringBuilder();
+ s.append("'");
+ if (port instanceof ProcessorPort) {
+ ProcessorPort processorPort = (ProcessorPort) port;
+ s.append(escapeName(processorPort.getParent().getName()));
+ s.append(":");
+ }
+ s.append(escapeName(port.getName()));
+ s.append("'");
+ return s.toString();
+ }
+
+ private String escapeName(String name) {
+ return name.replace("\\", "\\\\").replace("'", "\\'")
+ .replace(":", "\\:").replace("/", "\\/");
+ }
+
+ @Override
+ public Set<String> getMediaTypes() {
+ return Collections.singleton(TEXT_VND_TAVERNA_SCUFL2_STRUCTURE);
+ }
+
+ private void newLine(int indentLevel) {
+ sb.append("\n");
+ for (int i = 0; i < indentLevel; i++)
+ sb.append(" ");
+ }
+
+ private <T extends Named> List<T> sorted(NamedSet<T> namedSet) {
+ List<T> sorted = new ArrayList<>();
+ sorted.addAll(namedSet);
+ Collections.sort(sorted, new Comparator<T>() {
+ @Override
+ public int compare(T o1, T o2) {
+ return o1.getName().compareTo(o2.getName());
+ }
+ });
+ return sorted;
+ }
+
+ @Override
+ public synchronized void writeBundle(WorkflowBundle wb, File destination,
+ String mediaType) throws IOException {
+ destination.createNewFile();
+ try (BufferedOutputStream outputStream = new BufferedOutputStream(
+ new FileOutputStream(destination))) {
+ writeBundle(wb, outputStream, mediaType);
+ }
+ }
+
+ @Override
+ public void writeBundle(WorkflowBundle wfBundle, OutputStream output,
+ String mediaType) throws IOException {
+ OutputStreamWriter writer = new OutputStreamWriter(output, "utf-8");
+ try {
+ writer.write(bundleString(wfBundle));
+ } finally {
+ writer.close();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-language/blob/c08405cb/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/iterationstrategy/CrossProduct.java
----------------------------------------------------------------------
diff --git a/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/iterationstrategy/CrossProduct.java b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/iterationstrategy/CrossProduct.java
new file mode 100644
index 0000000..58895f0
--- /dev/null
+++ b/taverna-scufl2-api/src/main/java/org/apache/taverna/scufl2/api/iterationstrategy/CrossProduct.java
@@ -0,0 +1,106 @@
+package org.apache.taverna.scufl2.api.iterationstrategy;
+
+/*
+ * 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.ArrayList;
+import java.util.List;
+
+import org.apache.taverna.scufl2.api.common.AbstractCloneable;
+import org.apache.taverna.scufl2.api.common.Visitor;
+import org.apache.taverna.scufl2.api.common.WorkflowBean;
+
+
+/**
+ * @author Stian Soiland-Reyes
+ */
+@SuppressWarnings("serial")
+public class CrossProduct extends ArrayList<IterationStrategyNode> implements
+ IterationStrategyTopNode {
+ private IterationStrategyParent parent;
+
+ @Override
+ public boolean accept(Visitor visitor) {
+ if (visitor.visitEnter(this))
+ for (IterationStrategyNode strategy : this)
+ if (!strategy.accept(visitor))
+ break;
+ return visitor.visitLeave(this);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof CrossProduct && super.equals(o);
+ }
+
+ @Override
+ public IterationStrategyParent getParent() {
+ return parent;
+ }
+
+ @Override
+ public void setParent(IterationStrategyParent newParent) {
+ if (parent == newParent)
+ return;
+
+ if (parent != null) {
+ // Remove from old parent
+ if (parent instanceof IterationStrategyStack) {
+ IterationStrategyStack stack = (IterationStrategyStack) parent;
+ stack.remove(this);
+ } else if (parent instanceof DotProduct
+ || parent instanceof CrossProduct) {
+ @SuppressWarnings("unchecked")
+ List<IterationStrategyNode> parentList = (List<IterationStrategyNode>) parent;
+ parentList.remove(this);
+ } else
+ throw new IllegalArgumentException(
+ "Old parent must be a IterationStrategy, DotProduct or CrossProduct: "
+ + parent);
+ }
+
+ parent = newParent;
+
+ if (parent instanceof IterationStrategyStack) {
+ IterationStrategyStack stack = (IterationStrategyStack) parent;
+ if (!stack.contains(this))
+ stack.add(this);
+ } else if (parent instanceof DotProduct
+ || parent instanceof CrossProduct) {
+ @SuppressWarnings("unchecked")
+ List<IterationStrategyNode> parentList = (List<IterationStrategyNode>) parent;
+ if (!parentList.contains(this))
+ parentList.add(this);
+ } else
+ throw new IllegalArgumentException(
+ "Parent must be a IterationStrategy, DotProduct or CrossProduct: "
+ + parent);
+ }
+
+ @Override
+ public WorkflowBean clone() {
+ return AbstractCloneable.cloneWorkflowBean(this);
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + super.toString();
+ }
+}