All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.saxon.expr.TryCatch Maven / Gradle / Ivy

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2023 Saxonica Limited
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon.expr;

import net.sf.saxon.event.EventMonitor;
import net.sf.saxon.event.Outputter;
import net.sf.saxon.event.OutputterEventBuffer;
import net.sf.saxon.event.PipelineConfiguration;
import net.sf.saxon.expr.elab.*;
import net.sf.saxon.expr.instruct.BreakInstr;
import net.sf.saxon.expr.instruct.TailCall;
import net.sf.saxon.expr.parser.ContextItemStaticInfo;
import net.sf.saxon.expr.parser.ExpressionTool;
import net.sf.saxon.expr.parser.ExpressionVisitor;
import net.sf.saxon.expr.parser.RebindingMap;
import net.sf.saxon.lib.ErrorReporter;
import net.sf.saxon.om.*;
import net.sf.saxon.pattern.QNameTest;
import net.sf.saxon.s9api.XmlProcessingError;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trans.UncheckedXPathException;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.ItemType;
import net.sf.saxon.type.Type;
import net.sf.saxon.value.Cardinality;

import java.util.ArrayList;
import java.util.List;


/**
 * This class implements a try/catch expression. It consists of a try expression, and a sequence of Nametest/Catch
 * expression pairs. If the try expression succeeds, its result is returned; otherwise the error code of the
 * exception is matched against each of the Nametests in turn, and the first matching catch expression is
 * evaluated.
 */

public class TryCatch extends Expression {

    private final Operand tryOp;
    private final List catchClauses = new ArrayList<>();
    private boolean rollbackOutput;

    public TryCatch(Expression tryExpr) {
        this.tryOp = new Operand(this, tryExpr, OperandRole.SAME_FOCUS_ACTION);
    }

    public void addCatchExpression(QNameTest test, Expression catchExpr) {
        CatchClause clause = new CatchClause();
        clause.catchOp = new Operand(this, catchExpr, OperandRole.SAME_FOCUS_ACTION);
        clause.nameTest = test;
        catchClauses.add(clause);
    }

    public void setRollbackOutput(boolean rollback) {
        this.rollbackOutput = rollback;
    }

    public boolean isRollbackOutput() {
        return this.rollbackOutput;
    }

    /**
     * Get the "try" operand
     * @return the primary operand to be evaluated
     */

    public Operand getTryOperand() {
        return tryOp;
    }

    /**
     * Get the "try" expression
     *
     * @return the primary expression to be evaluated
     */
    public Expression getTryExpr() {
        return tryOp.getChildExpression();
    }


    /**
     * Get the list of catch clauses
     *
     * @return the list of catch clauses
     */
    public List getCatchClauses() {
        return catchClauses;
    }

    /**
     * Ask whether this expression is an instruction. In XSLT streamability analysis this
     * is used to distinguish constructs corresponding to XSLT instructions from other constructs,
     * typically XPath expressions.
     *
     * @return true (if this construct exists at all in an XSLT environment, then it represents an instruction)
     */
    @Override
    public boolean isInstruction() {
        return true;
    }

    /**
     * Ask whether common subexpressions found in the operands of this expression can
     * be extracted and evaluated outside the expression itself. The result is irrelevant
     * in the case of operands evaluated with a different focus, which will never be
     * extracted in this way, even if they have no focus dependency.
     *
     * @return false for this kind of expression
     */
    @Override
    public boolean allowExtractingCommonSubexpressions() {
        return false;
    }

    /**
     * Determine the cardinality of the function.
     */

    @Override
    protected int computeCardinality() {
        int card = getTryExpr().getCardinality();
        for (CatchClause catchClause : catchClauses) {
            card = Cardinality.union(card, catchClause.catchOp.getChildExpression().getCardinality());
        }
        return card;
    }

    /**
     * Determine the item type of the value returned by the function
     */

    /*@NotNull*/
    @Override
    public ItemType getItemType() {
        ItemType type = getTryExpr().getItemType();
        for (CatchClause catchClause : catchClauses) {
            type = Type.getCommonSuperType(type, catchClause.catchOp.getChildExpression().getItemType());
        }
        return type;
    }

    /**
     * Get the immediate sub-expressions of this expression, with information about the relationship
     * of each expression to its parent expression. Default implementation
     * returns a zero-length array, appropriate for an expression that has no
     * sub-expressions.
     *
     * @return an iterator containing the sub-expressions of this expression
     */
    @Override
    public Iterable operands() {
        List list = new ArrayList<>();
        list.add(tryOp);
        for (CatchClause cc : catchClauses) {
            list.add(cc.catchOp);
        }
        return list;
    }

    @Override
    public Expression optimize(ExpressionVisitor visitor, ContextItemStaticInfo contextInfo) throws XPathException {
        optimizeChildren(visitor, contextInfo);
        Expression e = getParentExpression();
        while (e != null) {
            if (e instanceof LetExpression && ExpressionTool.dependsOnVariable(getTryExpr(), new Binding[]{(LetExpression)e})) {
                ((LetExpression)e).setNeedsEagerEvaluation(true);
            }
            e = e.getParentExpression();
        }
        return this;
    }

    /**
     * An implementation of Expression must provide at least one of the methods evaluateItem(), iterate(), or process().
     * This method indicates which of these methods is provided directly. The other methods will always be available
     * indirectly, using an implementation that relies on one of the other methods.
     *
     * @return the implementation method, for example {@link #ITERATE_METHOD} or {@link #EVALUATE_METHOD} or
     * {@link #PROCESS_METHOD}
     */
    @Override
    public int getImplementationMethod() {
        return ITERATE_METHOD;
    }

    /**
     * Is this expression the same as another expression?
     *
     * @param other the expression to be compared with this one
     * @return true if the two expressions are statically equivalent
     */
    @Override
    public boolean equals(Object other) {
        return other instanceof TryCatch && ((TryCatch)other).tryOp.getChildExpression().isEqual(tryOp.getChildExpression())
                && ((TryCatch)other).catchClauses.equals(catchClauses);
    }

    /**
     * Hashcode supporting equals()
     */

    @Override
    protected int computeHashCode() {
        int h = 0x636b12a0;
        for (int i = 0; i < catchClauses.size(); i++) {
            h ^= catchClauses.get(i).hashCode()< {
                PipelineConfiguration pipe = output.getPipelineConfiguration();
                XPathContextMajor c1 = context.newContext();
                c1.createThreadManager();
                c1.setErrorReporter(new FilteringErrorReporter(context.getErrorReporter(), expr.catchClauses));
                //Outputter original = context.getReceiver();
                Outputter o2;
                if (expr.rollbackOutput) {
                    o2 = new OutputterEventBuffer();
                    o2.setPipelineConfiguration(pipe);
                } else {
                    o2 = new EventMonitor(output);
                    o2.setPipelineConfiguration(pipe);
                }
                try {
                    try {
                        TailCall tc = tryPush.processLeavingTail(o2, c1);
                        Expression.dispatchTailCall(tc);
                        c1.waitForChildThreads();
                        //context.setReceiver(original);
                        // check for xsl:break within xsl:try - test iterate-035
                        TailCallLoop.TailCallInfo tci = c1.getTailCallInfo();
                        if (tci instanceof BreakInstr) {
                            ((BreakInstr) tci).markContext(context);
                        }
                        if (expr.rollbackOutput) {
                            assert o2 instanceof OutputterEventBuffer;
                            ((OutputterEventBuffer) o2).replay(output);
                        }
                    } catch (UncheckedXPathException ue) {
                        throw ue.getXPathException();
                    }
                } catch (XPathException err) {
                    if (err.isGlobalError()) {
                        err.setIsGlobalError(false);
                    } else {
                        StructuredQName code = err.getErrorCodeQName();
                        if (code == null) {
                            code = new StructuredQName("saxon", NamespaceUri.SAXON, "XXXX9999");
                        }
                        for (CatchClause clause : expr.catchClauses) {
                            if (clause.nameTest.matches(code)) {
                                if (o2 instanceof EventMonitor && ((EventMonitor) o2).hasBeenWrittenTo()) {
                                    // rollback=no was specified, and output has been written, so we cannot recover
                                    String message = err.getMessage() +
                                            ". The error could not be caught, because rollback-output=no was specified, and output was already written to the result tree";
                                    XPathException xe = new XPathException(message, "XTDE3530");
                                    xe.setLocation(err.getLocator());
                                    xe.setXPathContext(context);
                                    throw xe;
                                }
                                Expression caught = clause.catchOp.getChildExpression();
                                XPathContextMajor c2 = context.newContext();
                                c2.setCurrentException(err);
                                // check for xsl:break within xsl:catch - test iterate-036
                                GroundedValue v = ExpressionTool.eagerEvaluate(caught, c2);
                                TailCallLoop.TailCallInfo tci = c2.getTailCallInfo();
                                if (tci instanceof BreakInstr) {
                                    ((BreakInstr) tci).markContext(context);
                                }
                                //noinspection Convert2MethodRef
                                SequenceTool.supply(v.iterate(), (ItemConsumer) item -> output.append(item));
                                return null;
                            }
                        }
                    }
                    err.setHasBeenReported(false);
                    throw err;

                }
                return null;
            };
        }

        @Override
        public PullEvaluator elaborateForPull() {
            TryCatch expr = (TryCatch)getExpression();
            SequenceEvaluator tryEval = expr.getTryExpr().makeElaborator().eagerly();
            return context -> {
                XPathContextMajor c1 = context.newContext();
                c1.createThreadManager();
                c1.setErrorReporter(new FilteringErrorReporter(context.getErrorReporter(), expr.catchClauses));
                try {
                    try {
                        // Need to do eager iteration of the first argument to flush any errors out
                        Sequence v = tryEval.evaluate(c1);
                        c1.waitForChildThreads();
                        // check for xsl:break within xsl:try - test iterate-035
                        TailCallLoop.TailCallInfo tci = c1.getTailCallInfo();
                        if (tci instanceof BreakInstr) {
                            ((BreakInstr) tci).markContext(context);
                        }
                        return v.iterate();
                    } catch (UncheckedXPathException ue) {
                        throw ue.getXPathException();
                    }
                } catch (XPathException err) {
                    if (err.isGlobalError()) {
                        err.setIsGlobalError(false);
                    } else {
                        StructuredQName code = err.getErrorCodeQName();
                        if (code == null) {
                            code = new StructuredQName("saxon", NamespaceUri.SAXON, "XXXX9999");
                        }
                        for (CatchClause clause : expr.catchClauses) {
                            if (clause.nameTest.matches(code)) {
                                Expression caught = clause.catchOp.getChildExpression();
                                XPathContextMajor c2 = context.newContext();
                                c2.setCurrentException(err);
                                // check for xsl:break within xsl:catch - test iterate-036
                                Sequence v = ExpressionTool.eagerEvaluate(caught, c2);
                                TailCallLoop.TailCallInfo tci = c2.getTailCallInfo();
                                if (tci instanceof BreakInstr) {
                                    ((BreakInstr) tci).markContext(context);
                                }
                                return v.iterate();
                            }
                        }
                    }
                    err.setHasBeenReported(false);
                    throw err;
                }
            };
        }

        @Override
        public ItemEvaluator elaborateForItem() {
            TryCatch expr = (TryCatch) getExpression();
            SequenceEvaluator tryEval = expr.getTryExpr().makeElaborator().eagerly();
            return context -> {
                XPathContext c1 = context.newMinorContext();
                try {
                    try {
                        return tryEval.evaluate(c1).head();
                    } catch (UncheckedXPathException e) {
                        throw e.getXPathException();
                    }
                } catch (XPathException err) {
                    if (err.isGlobalError()) {
                        err.setIsGlobalError(false);
                    } else {
                        StructuredQName code = err.getErrorCodeQName();
                        if (code == null) {
                            code = new StructuredQName("saxon", NamespaceUri.SAXON, "XXXX9999");
                        }
                        for (CatchClause clause : expr.catchClauses) {
                            if (clause.nameTest.matches(code)) {
                                Expression caught = clause.catchOp.getChildExpression();
                                XPathContextMajor c2 = context.newContext();
                                c2.setCurrentException(err);
                                return caught.evaluateItem(c2);
                            }
                        }
                    }
                    err.setHasBeenReported(false);
                    throw err;
                }
            };
        }
    }

    /**
     * An error listener that filters out reporting of any errors that are caught be the try/catch
     */

    private static class FilteringErrorReporter implements ErrorReporter {

        private final ErrorReporter base;
        private final List catchClauses;

        FilteringErrorReporter(ErrorReporter base, List catchClauses) {
            this.base = base;
            this.catchClauses = catchClauses;
        }

        private boolean isCaught(XmlProcessingError err) {
            StructuredQName code = err.getErrorCode().getStructuredQName();
            for (CatchClause clause : catchClauses) {
                if (clause.nameTest.matches(code)) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public void report(XmlProcessingError error) {
            if (error.isWarning() || !isCaught(error)) {
                base.report(error);
            }
        }
    }

    /**
     * Get the (partial) name of a class that supports streaming of this kind of expression
     *
     * @return the partial name of a class that can be instantiated to provide streaming support in Saxon-EE,
     * or null if there is no such class
     */
    @Override
    public String getStreamerName() {
        return "TryCatch";
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy