org.apache.taverna.scufl2.api.common.Scufl2Tools.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.taverna.scufl2.api.common.Scufl2Tools.java

Source

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.iterationstrategy.CrossProduct;
import org.apache.taverna.scufl2.api.iterationstrategy.IterationStrategyStack;
import org.apache.taverna.scufl2.api.iterationstrategy.PortNode;
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;
    }

    public void createDefaultIterationStrategyStack(Processor p) {
        p.setIterationStrategyStack(new IterationStrategyStack());
        CrossProduct crossProduct = new CrossProduct();
        for (InputProcessorPort in : p.getInputPorts()) {
            // As this is a NamedSet the above will always be in 
            // the same alphabetical order
            // FIXME: What about different Locales?
            crossProduct.add(new PortNode(crossProduct, in));
        }
        p.getIterationStrategyStack().add(crossProduct);
    }
}