Package com.xmlcalabash.model

Source Code of com.xmlcalabash.model.Step

/*
* XProcStep.java
*
* Copyright 2008 Mark Logic Corporation.
* Portions Copyright 2007 Sun Microsystems, Inc.
* All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common
* Development and Distribution License("CDDL") (collectively, the
* "License"). You may not use this file except in compliance with the
* License. You can obtain a copy of the License at
* https://xproc.dev.java.net/public/CDDL+GPL.html or
* docs/CDDL+GPL.txt in the distribution. See the License for the
* specific language governing permissions and limitations under the
* License. When distributing the software, include this License Header
* Notice in each file and include the License file at docs/CDDL+GPL.txt.
*/

package com.xmlcalabash.model;

import java.util.Collection;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Vector;
import java.util.List;

import com.xmlcalabash.util.MessageFormatter;
import net.sf.saxon.s9api.Axis;
import net.sf.saxon.s9api.QName;
import net.sf.saxon.s9api.XdmNode;
import com.xmlcalabash.core.XProcConstants;
import com.xmlcalabash.core.XProcRuntime;
import com.xmlcalabash.core.XProcException;
import net.sf.saxon.s9api.XdmNodeKind;
import net.sf.saxon.s9api.XdmSequenceIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
*
* @author ndw
*/
public class Step extends SourceArtifact {
    private static final QName cx_depend = new QName("cx",XProcConstants.NS_CALABASH_EX,"depend");
    private static final QName cx_depends = new QName("cx",XProcConstants.NS_CALABASH_EX,"depends");
    private static final QName cx_dependson = new QName("cx",XProcConstants.NS_CALABASH_EX,"dependson");

    protected QName stepType = null;
    protected String stepName = null;
    private boolean anonymous = false;
    protected Vector<Input> inputs = new Vector<Input> ();
    protected Vector<Output> outputs = new Vector<Output> ();
    private Vector<Option> options = new Vector<Option> ();
    private Vector<Log> logs = new Vector<Log> ();
    private Vector<Parameter> params = new Vector<Parameter> ();
    private HashSet<String> dependsOn = new HashSet<String> ();
    protected Environment env = null;
    protected Step parent = null;
    private int depth = -1;
    private boolean ordered = false;
    private static HashSet<String> anonNames = new HashSet<String> ();
    // FIXME: This should only be in compoundstep!
    Vector<Step> subpipeline = new Vector<Step>();
    protected DeclareStep declaration = null;
    protected Double version = null;

    /** Creates a new instance of Step */
    public Step(XProcRuntime xproc, XdmNode node, QName type) {
        super(xproc, node);
        stepType = type;
        stepName = anonymousName(node);
    }

    public Step(XProcRuntime xproc, XdmNode node, QName type, String name) {
        super(xproc, node);
        stepType = type;
        stepName = name;
        if (stepName == null) {
            stepName = anonymousName(node);
        }
    }

    public boolean isPipeline() {
        return false;
    }

    private synchronized String anonymousName(XdmNode node) {
        String stepName = recursiveAnonymousName(node);
        anonNames.add(stepName);
        return stepName;
    }

    private String recursiveAnonymousName(XdmNode node) {
        if (node.getParent().getNodeKind() == XdmNodeKind.DOCUMENT) {
            return "!1";
        } else {
            XdmSequenceIterator iter = node.axisIterator(Axis.PRECEDING_SIBLING);
            int count = 1;
            while (iter.hasNext()) {
                XdmNode pnode = (XdmNode) iter.next();
                if (pnode.getNodeKind() == XdmNodeKind.ELEMENT) {
                    count++;
                }
            }
            return anonymousName(node.getParent()) + "." + count;
        }
    }

    /*
    private String anonymousName(XdmNode node) {
        int count = 1;
        XdmSequenceIterator iter = node.axisIterator(Axis.PRECEDING_SIBLING);
        while (iter.hasNext()) {
            XdmNode pnode = (XdmNode) iter.next();
            if (pnode.getNodeKind() == XdmNodeKind.ELEMENT) {
                count++;
            }
        }
        XdmNode parent = node.getParent();
        if (parent != null && parent.getNodeKind() == XdmNodeKind.ELEMENT) {
            return anonymousName(parent) + "." + count;
        } else {
            return "!" + count;
        }
    }
    */

    public boolean isAnonymous() {
        return anonymous;
    }

    public void setDeclaration(DeclareStep decl) {
        declaration = decl;
    }

    public DeclareStep getDeclaration() {
        return declaration;
    }

    public boolean isPipelineCall() {
        return !declaration.isAtomic();
    }

    public QName getType() {
        return stepType;
    }

    public Step getStep() {
        return this;
    }

    protected void setVersion(Double version) {
        this.version = version;
    }

    public Double getVersion() {
        if (this.version == null) {
            if (parent != null) {
                return parent.getVersion();
            } else
                throw new UnsupportedOperationException("Step with no version or inherited version!?");
        } else {
            return version;
        }
    }

    public QName getDeclaredType() {
        // For p:declare-step and p:pipeline, this is different from the type.
        return stepType;
    }

    public String getName() {
        return stepName;
    }

    public XdmNode getNode() {
        return node;
    }
   
    public Step getPipeline() {
        Step parent = this;
        while (parent != null
               && parent.stepType != XProcConstants.p_declare_step) {
            parent = parent.parent;
        }
        return parent;
    }

    public boolean containsStep(String stepName) {
        return false; // atomic steps have no subpipelines
    }

    public void addStep(Step step) {
        step.parent = this;
        subpipeline.add(step);
    }

    public void setSubpipeline(Vector<Step> pipeline) {
        subpipeline = pipeline;
    }

    public Vector<Step> subpipeline() {
        return subpipeline;
    }

    public void addVariable(Variable variable) {
        throw new UnsupportedOperationException("You can only call addVariable() on a compound step");
    }

    public Collection<Variable> getVariables() {
        // Atomic steps have no getVariables
        return new Vector<Variable> ();
    }

    public void addInput(Input input) {
        input.setStep(this);
       
        for (Input current : inputs) {
            if (current.getPort().equals(input.getPort())) {
                throw XProcException.staticError(11, input.getNode(), "Input port name '" + input.getPort() + "' appears more than once.");
            }
        }
       
        inputs.add(input);
    }

    public Vector<Input> inputs() {
        return inputs;
    }
   
    public Input getInput(String portName) {
        for (Input input : inputs) {
            if (portName.equals(input.getPort())) {
                return input;
            }
        }
        return null;
    }
   
    public void addOutput(Output output) {
        output.setStep(this);

        for (Output current : outputs) {
            if (current.getPort().equals(output.getPort())) {
                throw XProcException.staticError(11, output.getNode(), "Output port name '" + output.getPort() + "' appears more than once.");
            }
        }

        outputs.add(output);
    }

    public Vector<Output> outputs() {
        return outputs;
    }

    public Output getPrimaryOutput() {
        int count = 0;
        Output defPrimary = null;
        for (Output output : outputs) {
            if ("#current".equals(output.getPort())) {
                // nop; this one never counts
            } else {
                if (output.getPrimary() || !output.getPrimarySet()) {
                    defPrimary = output;
                }
                count++;
                if (output.getPrimary()) {
                    return output;
                }
            }
        }

        if (count == 1) {
            return defPrimary;
        } else {
            return null;
        }
    }

    public Output getOutput(String portName) {
        for (Output output : outputs) {
            if (portName.equals(output.getPort())) {
                return output;
            }
        }
        return null;
    }
   
    public void addOption(Option option) {
        // FIXME: Is it worth making a hash for this?
        QName optName = option.getName();
        for (Option exoption : options) {
            if (optName.equals(exoption.getName())) {
                error(option.getNode(),"Duplication option name: " + optName,XProcConstants.staticError(4));
            }
        }
        options.add(option);
    }

    public Vector<Option> options() {
        return options;
    }

    public Option getOption(QName name) {
        for (Option option : options) {
            if (name.equals(option.getName())) {
                return option;
            }
        }
        return null;
    }

    public List<QName> getOptions() {
        Vector<QName> names = new Vector<QName> ();
        for (Option option : options) {
            names.add(option.getName());
        }
        return names;
    }
   
    public void addLog(Log log) {
        logs.add(log);
    }
   
    public Log getLog(String port) {
        for (Log log : logs)  {
            if (port.equals(log.getPort())) {
                return log;
            }
        }
        return null;
    }
   
    public void addParameter(Parameter param) {
        params.add(param);
    }

    public Parameter getParameter(QName name) {
        for (Parameter param : params) {
            if (name.equals(param.getName())) {
                return param;
            }
        }
        return null;
    }

    public List<QName> getParameters() {
        Vector<QName> names = new Vector<QName> ();
        for (Parameter param : params) {
            names.add(param.getName());
        }
        return names;
    }
   
    public Vector<Parameter> parameters() {
        return params;
    }
   
    public boolean loops() {
        return false;
    }

    public boolean insideALoop() {
        if (parent == null) {
            return false;
        }

        return parent.loops() || parent.insideALoop();
    }
   
    public Output getDefaultOutput() {
        // See if there's exactly one "ordinary" output
        int count = 0;
        Output defout = null;
        for (Output output : outputs) {
            if (!output.getPort().startsWith("#")) {
                if (output.getPrimary()) {
                    return output;
                }
                if (!output.getPort().endsWith("|")) {
                    count++;
                    defout = output;
                }
            }
        }
       
        if (count == 1 && (defout.getPrimary() || !defout.getPrimarySet())) {
            return defout;
        }
       
        return null;
    }
   
    protected void addDependency(String stepName) {
        dependsOn.add(stepName);
    }

    protected HashSet<String> getDependencies() {
        return dependsOn;
    }

    protected boolean dependsOn(String stepName) {
        return dependsOn.contains(stepName);
    }
   
    protected boolean matchesDeclaration() {
        boolean valid = true;
        Step decl = declaration;

        // FIXME: is this still true
        if (decl == null) { // must be the root pipeline
            return true;
        }
       
        Hashtable<String,Input> declInputs = new Hashtable<String,Input> ();
        for (Input input : decl.inputs()) {
            declInputs.put(input.getPort(), input);
        }
       
        for (Input input : inputs()) {
            String port = input.getPort();
            if (!port.startsWith("|")) {
                if (!declInputs.containsKey(port)) {
                    if (getVersion() == 1.0) {
                      error("Undeclared input port '" + port + "' on " + this, XProcConstants.staticError(10));
                      valid = false;
                    }
                } else {
                    input.setPrimary(declInputs.get(port).getPrimary());
                }
            }
        }

        Hashtable<String,Output> declOutputs = new Hashtable<String,Output>();
        for (Output output : decl.outputs()) {
            declOutputs.put(output.getPort(), output);
        }

        for (Output output : outputs()) {
            String port = output.getPort();
            if (!port.endsWith("|")) {
                if (!declOutputs.containsKey(port) && !declOutputs.containsKey("*")) {
                    error("Undeclared output port: " + port, XProcConstants.staticError(10));
                    valid = false;
                } else {
                    output.setPrimary(declOutputs.get(port).getPrimary());
                }
            }
        }

        return valid;
    }

    protected boolean validOptions() {
        HashSet<QName> names = new HashSet<QName> ();
        boolean valid = true;
       
        for (Option p : options) {
            valid = valid && p.valid(env);
            QName pName = p.getName();
            if (pName == null) {
                valid = false;
                error("Option without name", XProcConstants.staticError(38));
            } else {
                if (names.contains(pName)) {
                    valid = false;
                    error("Duplicate option name: " + pName, XProcConstants.staticError(4));
                } else {
                    names.add(pName);
                }
            }
        }

        // Do we have valid options?
        Step decl = declaration;
        if (decl != null) {
            // Do we have all the required options?
            for (Option doption : decl.options()) {
                if (doption.getRequired()) {
                    if (getOption(doption.getName()) == null) {
                        valid = false;
                        error("Required option not specified: " + doption.getName(), XProcConstants.staticError(18));
                    }
                }
            }

            // Are all the options we have allowed?
            Vector<Option> okOpts = new Vector<Option> ();
            for (Option option : options()) {
                Option doption = decl.getOption(option.getName());
                if (doption == null) {
                    if (getVersion() > 1.0) {
                        // nop
                    } else {
                        valid = false;
                        error("Undeclared option specified: " + option.getName(), XProcConstants.staticError(10));
                    }
                } else {
                    okOpts.add(option);
                }
            }
            // Make sure bogus 2.0 options are removed, otherwise we have to test for them elsewhere
            options = okOpts;
        }
       
        return valid;
    }

    protected boolean validParams() {
        HashSet<QName> names = new HashSet<QName> ();
        boolean valid = true;
       
        for (Parameter p : params) {
            valid = valid && p.valid(env);
            QName pName = p.getName();
            if (pName == null) {
                valid = false;
                error("Parameter without name", XProcConstants.staticError(38));
            } else {
                if (names.contains(pName)) {
                    valid = false;
                    error("Duplicate parameter name: " + pName, XProcConstants.staticError(4));
                } else {
                    names.add(pName);
                }
            }
           
            String port = p.getPort();
            if (port == null) {
                for (Input input : inputs()) {
                    if (input.getParameterInput() && input.getPrimary()) {
                        port = input.getPort();
                    }
                }
                if (port == null) {
                    for (Input input : inputs()) {
                        if (input.getParameterInput()) {
                            if (port != null) {
                                error("Port not specified and multiple parameter input ports", XProcException.err_E0001);
                            }
                            port = input.getPort();
                        }
                    }
                }
            }

            if (port == null) {
                valid = false;
                error("Port not specified and no primary parameter input port", XProcException.err_E0001);
            } else {
                Input input = getInput(port);
                if (input == null || !input.getParameterInput()) {
                    valid = false;
                    error("Port is not a parameter input port: " + port, XProcException.err_E0001);
                }
            }
        }
       
        return valid;
    }

    protected boolean validBindings() {
        boolean valid = true;
        boolean seenPrimaryDoc = false;
        boolean seenPrimaryParam = false;
      
        for (Input input : inputs()) {
            if (!input.getPort().startsWith("|") && input.getPrimary()) {
                if (input.getParameterInput()) {
                    if (seenPrimaryParam) {
                        error("At most one primary parameter input port is allowed", XProcConstants.staticError(30));
                    }
                    seenPrimaryParam = true;
                } else {
                    if (seenPrimaryDoc) {
                        error("At most one primary input port is allowed", XProcConstants.staticError(30));
                    }
                    seenPrimaryDoc = true;
                }
            }
           
            if (!checkBinding(input)) {
                valid = false;
            }
        }

        for (Option option: options()) {
            // p:option elements aren't allowed to have bindings
            if (XProcConstants.p_with_option.equals(option.getNode().getNodeName())) {
                if (!checkOptionBinding(option, true)) {
                    valid = false;
                }
            }
        }

        for (Parameter param: parameters()) {
            if (!checkOptionBinding(param, true)) {
                valid = false;
            }
        }

        return valid;
    }

    protected void checkDuplicateVars(HashSet<QName> vars) {
        for (Variable var : getVariables()) {
            if (vars.contains(var.getName())) {
                throw XProcException.staticError(4, getNode(), "Duplicate variable name: " + var.getName());
            } else {
                vars.add(var.getName());
            }
        }
    }

    protected boolean checkBinding(Input input) {
        boolean valid = true;

        logger.trace(MessageFormatter.nodeMessage(node, "Check bindings for " + input.getPort() + " on " + getName()));

        if (input.getBinding().size() == 0) {
            if (input.getParameterInput()) {
                if (input.getPrimary()) {
                    // We need to bind to the primary parameter input of our ancestor pipeline
                    Step pipeline = getPipeline();
                    Input paramsin = null;
                    int count = 0;
                    for (Input pinput : pipeline.inputs()) {
                        if (pinput.getParameterInput()) {
                            count++;
                            if (paramsin == null || pinput.getPrimary()) {
                                paramsin = pinput;
                            }
                        }
                    }
               
                    if (paramsin == null || (!paramsin.getPrimary() && count > 1)) {
                        if (params.size() > 0) {
                            // There's at least one explicit with-param, so unbound is ok.
                            EmptyBinding binding = new EmptyBinding(runtime, node);
                            input.addBinding(binding);
                        } else {
                            valid = false;
                            error("Parameter input " + input.getPort() + " unbound on " + getType() + " step named " + getName() + " and no default binding available.", XProcConstants.staticError(55));
                        }
                    } else {
                        PipeNameBinding binding = new PipeNameBinding(runtime, node);
                        binding.setStep(pipeline.getName());
                        binding.setPort(paramsin.getPort());
                        input.addBinding(binding);
                    }
                } else {
                    EmptyBinding binding = new EmptyBinding(runtime, node);
                    input.addBinding(binding);
                }
            } else {
                Port port = env.getDefaultReadablePort();
           
                if (input.getPort().startsWith("|")) {
                    if (subpipeline.size() > 0) {
                        // This needs to be bound to the output of the last step.
                        Step substep = subpipeline.get(subpipeline.size()-1);
                        port = substep.getDefaultOutput();
                    } else if (isPipelineCall()) {
                        // This doesn't need/shouldn't have a binding...the pipeilne call will take care of it.
                        return true;
                    }
                }

                if (input.getPrimary() && port != null) {
                    String stepName = port.getStep().getName();
                    String portName = port.getPort();

                    PipeNameBinding binding = new PipeNameBinding(runtime, node);
                    binding.setStep(stepName);
                    binding.setPort(portName);

                    //errhandler.warning(node, "Manufactured binding for " + input.getPort() + " to " + portName + " on " + stepName);
                    input.addBinding(binding);
                } else {
                    // If there's a default binding, use it. FIXME: Is it safe to copy the binding like this?
                    Input declIn = declaration.getInput(input.getPort());
                    if (declIn.getBinding().size() != 0) {
                        input.setSelect(declIn.getSelect());
                        for (Binding binding : declIn.getBinding()) {
                            input.addBinding(binding);
                        }
                    } else {
                        valid = false;
                        error("Input " + input.getPort() + " unbound on " + getType() + " step named " + getName() + " and no default binding available.", XProcConstants.staticError(32));
                    }
                }
            }
        }

        boolean catchErrors = false;
        for (Binding binding : input.getBinding()) {
            if (binding.getBindingType() == Binding.PIPE_NAME_BINDING) {
                PipeNameBinding pipe = (PipeNameBinding) binding;
                Output output = env.readablePort(pipe.getStep(), pipe.getPort());
                if (output == null) {
                    Step fromstep = env.visibleStep(pipe.getStep());

                    if (fromstep == null) {
                        throw XProcException.staticError(22,binding.getNode(),"No step named \"" + pipe.getStep() + "\" is visible here.");
                    }

                    if ("error".equals(pipe.getPort()) && XProcConstants.p_catch.equals(fromstep.getType())) {
                        catchErrors = true;
                    } else {
                        if (XProcConstants.NS_XPROC.equals(fromstep.getType().getNamespaceURI())
                                && getVersion() > 1.0) {
                            // Nevermind, it's ok to bind to unknown ports in this case
                            input.setSequence(true);
                        } else {
                            error(binding.getNode(),"No port named \"" + pipe.getPort() + "\" on step named \"" + pipe.getStep() + "\"", XProcConstants.staticError(22));
                            valid = false;
                        }
                    }
                } else if (!pipe.getPort().equals(output.getPort())) {
                    Step step = env.visibleStep(pipe.getStep());
                    if (XProcConstants.p_viewport.equals(step.getType())) {
                        // FIXME: This hack is just for p:viewport, do it somewhere else and better
                        pipe.setPort(output.getPort());
                    }
                }
            }
        }

        if (catchErrors) {
            Vector<Binding> newBindings = new Vector<Binding> ();
            Step fromstep = null;
            for (Binding binding : input.getBinding()) {
                catchErrors = false;
                if (binding.getBindingType() == Binding.PIPE_NAME_BINDING) {
                    PipeNameBinding pipe = (PipeNameBinding) binding;
                    Output output = env.readablePort(pipe.getStep(), pipe.getPort());
                    if (output == null) {
                        fromstep = env.visibleStep(pipe.getStep());
                        if ("error".equals(pipe.getPort()) && XProcConstants.p_catch.equals(fromstep.getType())) {
                            catchErrors = true;
                        }
                    }
                }
                if (catchErrors) {
                    newBindings.add(new ErrorBinding(fromstep.getXProc(), fromstep.getNode()));
                } else {
                    newBindings.add(binding);
                }
            }
            input.clearBindings();
            for (Binding binding : newBindings) {
                input.addBinding(binding);
            }
        }

        return valid;
    }

    protected boolean checkOptionBinding(EndPoint endpoint) {
        return checkOptionBinding(endpoint, false);
    }

    protected boolean checkOptionBinding(EndPoint endpoint, boolean defEmpty) {
        boolean valid = true;

        logger.trace(MessageFormatter.nodeMessage(node, "Check bindings for " + endpoint + " on " + getName()));

        if (endpoint.getBinding().size() == 0) {
            Port port = env.getDefaultReadablePort();

            if (port == null) {
                if (defEmpty) {
                    EmptyBinding empty = new EmptyBinding(runtime, node);
                    endpoint.addBinding(empty);
                } else {
                    valid = false;
                    error("" + endpoint + " unbound on " + getType() + " step named " + getName() + " and no default readable port.", XProcConstants.staticError(32));
                }
            } else {
                String stepName = port.getStep().getName();
                String portName = port.getPort();

                PipeNameBinding binding = new PipeNameBinding(runtime, node);
                binding.setStep(stepName);
                binding.setPort(portName);

                //errhandler.warning(node, "Manufactured binding for " + input.getPort() + " to " + portName + " on " + stepName);
                endpoint.addBinding(binding);
            }
        }

        boolean catchErrors = false;
        for (Binding binding : endpoint.getBinding()) {
            if (binding.getBindingType() == Binding.PIPE_NAME_BINDING) {
                PipeNameBinding pipe = (PipeNameBinding) binding;
                Output output = env.readablePort(pipe.getStep(), pipe.getPort());
                if (output == null) {
                    Step fromstep = env.visibleStep(pipe.getStep());
                    if ("error".equals(pipe.getPort()) && XProcConstants.p_catch.equals(fromstep.getType())) {
                        catchErrors = true;
                    } else {
                        error("Unreadable port: " + pipe.getPort() + " on " + pipe.getStep(), XProcConstants.staticError(22));
                        valid = false;
                    }
                } else {
                    if (XProcConstants.p_variable.equals(endpoint.getNode().getNodeName())) {
                        Step pipeStep = env.visibleStep(pipe.getStep());
                        Step container = pipeStep.parent;
                        if (container == this) {
                            throw XProcException.staticError(19, endpoint.getNode(), "Variable binding to " + pipe.getPort() + " on " + pipe.getStep() + " not allowed.");
                        }
                    }
                }
            }
        }

        if (catchErrors) {
            Vector<Binding> newBindings = new Vector<Binding> ();
            Step fromstep = null;
            for (Binding binding : endpoint.getBinding()) {
                catchErrors = false;
                if (binding.getBindingType() == Binding.PIPE_NAME_BINDING) {
                    PipeNameBinding pipe = (PipeNameBinding) binding;
                    Output output = env.readablePort(pipe.getStep(), pipe.getPort());
                    if (output == null) {
                        fromstep = env.visibleStep(pipe.getStep());
                        if ("error".equals(pipe.getPort()) && XProcConstants.p_catch.equals(fromstep.getType())) {
                            catchErrors = true;
                        }
                    }
                }
                if (catchErrors) {
                    newBindings.add(new ErrorBinding(fromstep.getXProc(), fromstep.getNode()));
                } else {
                    newBindings.add(binding);
                }
            }
            endpoint.clearBindings();
            for (Binding binding : newBindings) {
                endpoint.addBinding(binding);
            }
        }

        return valid;
    }

    protected void checkForBindings(HashSet<Output> outputs) {
        for (Input input : inputs()) {
            for (Binding binding : input.bindings) {
                if (binding.getBindingType() == Binding.PIPE_NAME_BINDING) {
                    PipeNameBinding b = (PipeNameBinding) binding;
                    Output output = env.readablePort(b.getStep(), b.getPort());
                    if (outputs.contains(output)) {
                        outputs.remove(output);
                    } else {
                        // Doesn't matter. Must be legit but doesn't help us.
                    }
                }
            }
        }

        for (Option option : options()) {
            for (Binding binding : option.bindings) {
                if (binding.getBindingType() == Binding.PIPE_NAME_BINDING) {
                    PipeNameBinding b = (PipeNameBinding) binding;
                    Output output = env.readablePort(b.getStep(), b.getPort());
                    if (outputs.contains(output)) {
                        outputs.remove(output);
                    } else {
                        // Doesn't matter. Must be legit but doesn't help us.
                    }
                }
            }
        }

        for (Parameter param : parameters()) {
            for (Binding binding : param.bindings) {
                if (binding.getBindingType() == Binding.PIPE_NAME_BINDING) {
                    PipeNameBinding b = (PipeNameBinding) binding;
                    Output output = env.readablePort(b.getStep(), b.getPort());
                    if (outputs.contains(output)) {
                        outputs.remove(output);
                    } else {
                        // Doesn't matter. Must be legit but doesn't help us.
                    }
                }
            }
        }
    }

    public boolean orderSteps() {
        boolean valid = true;

        if (ordered) {
            return true;
        }
        ordered = true;

        for (Step step : subpipeline) {
            if (!step.orderSteps()) {
                valid = false;
            }
        }

        logger.trace(MessageFormatter.nodeMessage(node, "Checking step order for " + getName()));

        if (getExtensionAttribute(cx_depend) != null
            || getExtensionAttribute(cx_depends) != null
            || getExtensionAttribute(cx_dependson) != null) {
            throw new XProcException(getNode(), "The correct spelling of the depends-on attribute is cx:depends-on.");
        }

        String dependsOn = getExtensionAttribute(XProcConstants.cx_depends_on);
        if (dependsOn != null) {
            Step step = env.visibleStep(dependsOn);
            if (step == null) {
                throw new XProcException(getNode(), "The value of cx:depends-on must be the name of an in-scope step: " + dependsOn);
            }
            addDependency(dependsOn);
        }

        for (Input input : inputs) {
            for (Binding binding : input.getBinding()) {
                if (binding.getBindingType() == Binding.PIPE_NAME_BINDING) {
                    PipeNameBinding pipe = (PipeNameBinding) binding;
                    logger.trace(MessageFormatter.nodeMessage(node, getName() + " input " + input.getPort() + " depends on " + pipe.getStep()));
                    addDependency(pipe.getStep());
                }
            }
        }
       
        // I don't think output bindings matter...
       
        for (Parameter param : params) {
            for (Binding binding : param.getBinding()) {
                if (binding.getBindingType() == Binding.PIPE_NAME_BINDING) {
                    PipeNameBinding pipe = (PipeNameBinding) binding;
                    logger.trace(MessageFormatter.nodeMessage(node, getName() + " param depends on " + pipe.getStep()));
                    addDependency(pipe.getStep());
                }
            }
        }

        for (Option option : options) {
            for (Binding binding : option.getBinding()) {
                if (binding.getBindingType() == Binding.PIPE_NAME_BINDING) {
                    PipeNameBinding pipe = (PipeNameBinding) binding;
                    logger.trace(MessageFormatter.nodeMessage(node, getName() + " option depends on " + pipe.getStep()));
                    addDependency(pipe.getStep());
                }
            }
        }

        // HACK! This should all be refactored so that this isn't necessary!
        checkVariables();

        // Update step dependencies and find roots...
        Vector<Step> roots = new Vector<Step> ();
        for (Step step : subpipeline) {
            HashSet<String> deps = step.getDependencies();
            boolean root = true;
            for (String stepName : deps) {
                if (!stepName.equals(getName())) {
                    logger.trace(MessageFormatter.nodeMessage(node, getName() + " step " + step.getName() + " depends on " + stepName));
                    addDependency(stepName);
                }
                if (containsStep(stepName)) {
                    root = false;
                }
            }
            if (root) {
                logger.trace(MessageFormatter.nodeMessage(node, "==> " + step.getName() + " is a graph root of " + getName()));
                roots.add(step);
                step.depth = 0;
            }
        }

        if (subpipeline.size() > 0) {
            if (roots.size() == 0) {
                error("No roots in " + getName(), XProcConstants.staticError(1));
                valid = false;
            } else {
                // Now find the dependency order for the substeps...
                int depth = 1;
                boolean noloops = true;
                while (noloops && roots.size() > 0) {
                    Vector<Step> nextWave = new Vector<Step> ();
                    for (Step root : roots) {
                        for (Step step : subpipeline) {
                            if (step.dependsOn(root.getName())) {
                                logger.trace(MessageFormatter.nodeMessage(node, "XProcStep " + step.getName() + " depends on " + root.getName() + "(step.depth="+step.depth+", depth="+depth+")"));
                                if ((step.depth < 0) || (depth >= step.depth)) {
                                    step.depth = depth;
                                    logger.trace(MessageFormatter.nodeMessage(node, step.getName() + " gets depth " + depth));
                                    nextWave.add(step);
                                } else {
                                    noloops = false;
                                    error("Loop in subpipeline: " + step.getName() + " points back to " + root.getName(), XProcConstants.staticError(1));
                                }
                            } else {
                                logger.trace(MessageFormatter.nodeMessage(node, "XProcStep " + step.getName() + " does not depend on " + root.getName()));
                            }
                        }
                    }
                    roots = nextWave;
                    depth++;
                }

                if (noloops) {
                    for (Step step : subpipeline) {
                        if (step.depth < 0) {
                            noloops = false;
                            error("Closed loop in subpipeline involves: " + step.getName(), XProcConstants.staticError(1));
                        }
                    }
                }
               
                valid = valid && noloops;
               
                if (valid) {
                    // Sort the subpipeline into a reasonable order
                    // FIXME: do a real sort, for crying out loud
                    Vector<Step> sorted = new Vector<Step> ();
                    int wave = 0;
                    while (wave < depth) {
                        for (Step step : subpipeline) {
                            if (step.depth == wave) {
                                sorted.add(step);
                            }
                        }
                        wave++;
                    }
                    subpipeline = sorted;
                }
            }
        }

        return valid;
    }

    public void checkVariables() {
        // nop; CompoundStep overrides this...
    }

    public boolean valid() {
        boolean valid = validParams();
       
        if (!matchesDeclaration()) {
            valid = false;
        }

        if (!validOptions()) {
            valid = false;
        }
       
        if (!validBindings()) {
            valid = false;
        }

        for (Log log : logs) {
            Output output = getOutput(log.getPort());
            if (output == null) {
                error("A p:log specified for a bad port: " + log.getPort(), XProcConstants.staticError(26));
                valid = false;
            }
        }

        if (env.countVisibleSteps(getName()) > 1) {
            error("Duplicate step name: " + getName(), XProcConstants.staticError(2));
            valid = false;
        }
       
        return valid;
    }

    protected void augmentIO() {
        Step decl = declaration;
       
        if (decl == null) {
            throw new UnsupportedOperationException("Unexpected step type.");
        }

        Hashtable<String,Input> declInputs = new Hashtable<String,Input> ();
        for (Input input : decl.inputs()) {
            declInputs.put(input.getPort(), input);
        }

        // Calculate the next position
        int position = 0;
        for (Input input : inputs()) {
            position++;
        }
        for (Parameter param : parameters()) {
            position++;
        }
       
        // Manufacture inputs for all the missing inputs; they'll get default bindings later...
        for (String portName : declInputs.keySet()) {
            Input dinput = declInputs.get(portName);
            Input input = getInput(portName);
            if (input == null) {
                logger.trace(MessageFormatter.nodeMessage(node, "Added " + portName + " input to " + getName()));
                input = new Input(runtime, node);
                input.setPort(portName);
                input.setParameterInput(dinput.getParameterInput());
                if (dinput.getPrimarySet()) {
                    input.setPrimary(dinput.getPrimary());
                }
                input.setSequence(dinput.getSequence());
                input.setPosition(++position);
                addInput(input);
            } else {
                input.setParameterInput(dinput.getParameterInput());
                input.setPrimary(dinput.getPrimary());
                input.setSequence(dinput.getSequence());
            }
        }
       
        Hashtable<String,Output> declOutputs = new Hashtable<String,Output>();
        for (Output output : decl.outputs()) {
            declOutputs.put(output.getPort(), output);
        }

        // Manufacture outputs for all the missing outputs; they'll get default bindings later...
        for (String portName : declOutputs.keySet()) {
            Output doutput = declOutputs.get(portName);
            Output output = getOutput(portName);
            if (output == null) {
                logger.trace(MessageFormatter.nodeMessage(node, "Added " + portName + " output to " + getName()));
                output = new Output(runtime, node);
                output.setPort(portName);
                output.setSequence(doutput.getSequence());
                if (doutput.getPrimarySet()) {
                    output.setPrimary(doutput.getPrimary());
                }
                addOutput(output);
            }
        }
    }

    protected void augmentOptions() {
        Step decl = declaration;
       
        if (decl == null) {
            throw new UnsupportedOperationException("Unexpected step type: " + getType());
        }

        Hashtable<QName,Option> declOptions = new Hashtable<QName,Option> ();
        for (Option option : decl.options()) {
            declOptions.put(option.getName(), option);
        }
       
        // Add any options that have default values...
        for (QName oname : declOptions.keySet()) {
            Option doption = declOptions.get(oname);
            Option option = getOption(oname);

            if (option == null) {
                if (doption.getSelect() != null || doption.getBinding().size() != 0) {
                    addOption(doption);
                }
            } else {
                option.setType(doption.getType(), doption.getNode()); // copy the type over
            }
        }
    }

    public void augment() {
        augmentIO();
        augmentOptions();
    }

    public void patchPipeBindings() {
        for (Input input : inputs) {
            patchInputBindings(input);
        }

        for (Parameter param : params) {
            patchInputBindings(param);
        }

        for (Option option : options) {
            patchInputBindings(option);
        }

        for (Variable var : getVariables()) {
            patchInputBindings(var);
        }

        for (Step step : subpipeline) {
            step.patchPipeBindings();
        }
    }

    protected void patchInputBindings(EndPoint endpoint) {
        Vector<Binding> bindings = endpoint.getBinding();
        for (int bpos = 0; bpos < bindings.size(); bpos++) {
            Binding binding = bindings.get(bpos);
           
            if (binding.getBindingType() == Binding.PIPE_NAME_BINDING) {
                PipeNameBinding pipename = (PipeNameBinding) binding;
                PipeBinding pipe = new PipeBinding(runtime, pipename.node);
                   
                Output source = env.readablePort(pipename.getStep(), pipename.getPort());
                pipe.setOutput(source);
                pipe.setInput(endpoint);

                //logger.trace(MessageFormatter.nodeMessage(node, "Patching " + pipename + " : " + pipe + " " + endpoint + " to " + source));

                bindings.set(bpos, pipe);
                if (source != null) {
                    source.addBinding(pipe);
                }
            }
        }
    }
   
    protected void setEnvironment(Environment newEnvironment) {
        env = newEnvironment;
    }
   
    protected void patchEnvironment(Environment env) {
        // nop;
    }

    public Environment getEnvironment() {
        return env;
    }

    @Override
    public String toString() {
        String str = null;
        if (stepName.startsWith("#")) {
            str = "anonymous step " + stepType;
        } else {
            str = "step " + stepType + " named " + stepName;
        }
        if (node.getLineNumber() > 0) {
            str += " at " + node.getDocumentURI() + ":" + node.getLineNumber();
        } else {
            str += " in " + node.getDocumentURI();
        }
        return str;
    }

    public void dump() {
        logger.trace("============================================================================");
        dump(0);
    }
   
    protected void dump(int depth) {
        String indent = "";
        for (int count = 0; count < depth; count++) {
            indent += " ";
        }

        if (getType().getNamespaceURI().equals(XProcConstants.NS_XPROC)) {
            logger.trace(indent + getType().getLocalName() + " " + getName());
        } else {
            logger.trace(indent + "XProcStep " + getName() + " (" + getType() + ")");
        }
       
        for (Input input : inputs) {
            input.dump(depth+2);
        }
        for (Output output : outputs) {
            output.dump(depth+2);
        }
        for (Parameter param : params) {
            param.dump(depth+2);
        }
        for (Option option : options) {
            option.dump(depth+2);
        }
        for (Variable var : getVariables()) {
            var.dump(depth+2);
        }
        for (Step step : subpipeline) {
            step.dump(depth+2);
        }
    }
}
TOP

Related Classes of com.xmlcalabash.model.Step

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.