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

net.sf.saxon.expr.flwor.FLWORExpression Maven / Gradle / Ivy

There is a newer version: 10.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2013 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.flwor;



import net.sf.saxon.Controller;
import net.sf.saxon.TypeCheckerEnvironment;
import net.sf.saxon.event.SequenceOutputter;
import net.sf.saxon.event.SequenceReceiver;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.parser.*;
import net.sf.saxon.om.Item;
import net.sf.saxon.om.SequenceIterator;
import net.sf.saxon.query.QueryModule;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trace.Location;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.util.FastStringBuffer;
import net.sf.saxon.type.ItemType;
import net.sf.saxon.type.TypeHierarchy;
import net.sf.saxon.value.SequenceType;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
 * This class represents a FLWOR expression, evaluated using tuple streams
 */
public class FLWORExpression extends Expression {

    /*@NotNull*/
    public List clauses;
    /*@NotNull*/
    public Expression returnClause;

    public FLWORExpression(/*@NotNull*/ List clauses, /*@NotNull*/ Expression returnClause) {
        this.clauses = clauses;
        this.returnClause = returnClause;
    }

    /**
     * Get the list of clauses of the FLWOR expression, in the order they are written.
     * This excludes the return clause
     *
     * @return the list of clauses
     */
    /*@NotNull*/
    public List getClauseList() {
        return clauses;
    }

    private static boolean isLoopingClause(Clause c) {
        return c.getClauseKey() == Clause.FOR || c.getClauseKey() == Clause.GROUPBYCLAUSE || c.getClauseKey() == Clause.WINDOW;
    }

    /**
     * Get the return clause of the FLWOR expression
     *
     * @return the expression contained in the return clause
     */

    /*@NotNull*/
    public Expression getReturnClause() {
        return returnClause;
    }

    /**
     * Determine whether a given variable binding belongs to this FLWOR expression
     *
     * @param binding the binding being sought
     * @return true if this binding belongs to one of the clauses of this FLWOR expression
     */

    public boolean hasVariableBinding(Binding binding) {
        for (Clause c : clauses) {
            if (clauseHasBinding(c, binding)) {
                return true;
            }
        }
        return false;
    }

    private boolean clauseHasBinding(Clause c, Binding binding) {
        for (Binding b : c.getRangeVariables()) {
            if (b == binding) {
                return true;
            }
        }
        return false;
    }

    /**
     * Simplify an expression. This performs any static optimization (by rewriting the expression
     * as a different expression). The default implementation does nothing.
     *
     * @param visitor an expression visitor
     * @return the simplified expression
     * @throws net.sf.saxon.trans.XPathException
     *          if an error is discovered during expression
     *          rewriting
     */
    /*@NotNull*/
    @Override
    public Expression simplify(final ExpressionVisitor visitor) throws XPathException {
        ExpressionProcessor simplifier = new ExpressionProcessor() {
            public Expression processExpression(Expression expr) throws XPathException {
                return visitor.simplify(expr);
            }
        };
        for (Clause c : clauses) {
            c.processSubExpressions(simplifier);
        }
        returnClause = visitor.simplify(returnClause);
        return this;
    }

    /**
     * Perform type checking of an expression and its subexpressions. This is the second phase of
     * static optimization.
     * 

*

This checks statically that the operands of the expression have * the correct type; if necessary it generates code to do run-time type checking or type * conversion. A static type error is reported only if execution cannot possibly succeed, that * is, if a run-time type error is inevitable. The call may return a modified form of the expression.

*

*

This method is called after all references to functions and variables have been resolved * to the declaration of the function or variable. However, the types of such functions and * variables may not be accurately known if they have not been explicitly declared.

*

*

If the implementation returns a value other than "this", then it is required to ensure that * the location information in the returned expression have been set up correctly. * It should not rely on the caller to do this, although for historical reasons many callers do so.

* * @param visitor an expression visitor * @param contextItemType the static type of "." at the point where this expression is invoked. * The parameter is set to null if it is known statically that the context item will be undefined. * If the type of the context item is not known statically, the argument is set to * {@link net.sf.saxon.type.Type#ITEM_TYPE} * @return the original expression, rewritten to perform necessary run-time type checks, * and to perform other type-related optimizations * @throws net.sf.saxon.trans.XPathException * if an error is discovered during this phase * (typically a type error) */ /*@NotNull*/ @Override public Expression typeCheck(final ExpressionVisitor visitor, final ExpressionVisitor.ContextItemType contextItemType) throws XPathException { ExpressionProcessor typeChecker = new ExpressionProcessor() { public Expression processExpression(Expression expr) throws XPathException { return visitor.typeCheck(expr, contextItemType); } }; for (int i = 0; i < clauses.size(); i++) { clauses.get(i).processSubExpressions(typeChecker); clauses.get(i).typeCheck(visitor); LocalVariableBinding[] bindings = clauses.get(i).getRangeVariables(); for (Binding b : bindings) { List references = new ArrayList(); for (int j = i; j < clauses.size(); j++) { clauses.get(j).gatherVariableReferences(visitor, b, references); } ExpressionTool.gatherVariableReferences(returnClause, b, references); clauses.get(i).refineVariableType(visitor, references, returnClause); } } returnClause = visitor.typeCheck(returnClause, contextItemType); return this; } /** * Determine whether this expression implements its own method for static type checking * * @return true - this expression has a non-trivial implementation of the staticTypeCheck() * method */ public boolean implementsStaticTypeCheck() { for (Clause c : clauses) { switch (c.getClauseKey()) { case Clause.LET: case Clause.WHERE: continue; default: return false; } } return true; } /** * Static type checking for let expressions is delegated to the expression itself, * and is performed on the "return" expression, to allow further delegation to the branches * of a conditional * @param req the required type * @param backwardsCompatible true if backwards compatibility mode applies * @param role the role of the expression in relation to the required type * @param visitor an expression visitor * @return the expression after type checking (perhaps augmented with dynamic type checking code) * @throws XPathException if failures occur, for example if the static type of one branch of the conditional * is incompatible with the required type */ public Expression staticTypeCheck(SequenceType req, boolean backwardsCompatible, RoleLocator role, TypeCheckerEnvironment visitor) throws XPathException { // only called if implementsStaticTypeCheck() returns true returnClause = TypeChecker.staticTypeCheck(returnClause, req, backwardsCompatible, role, visitor); return this; } /** * This method is required to refine the variabletype of the returnClause * Repeated code in the Clause sub-classes. Room here for commoning-up code * */ /** * Determine the data type of the items returned by the expression. * * @param th the type hierarchy cache * @return a value such as Type.STRING, Type.BOOLEAN, Type.NUMBER, * Type.NODE, or Type.ITEM (meaning not known at compile time) */ /*@NotNull*/ @Override public ItemType getItemType(TypeHierarchy th) { return returnClause.getItemType(th); } /** * Compute the static cardinality of this expression * * @return the computed cardinality, as one of the values {@link net.sf.saxon.expr.StaticProperty#ALLOWS_ZERO_OR_ONE}, * {@link net.sf.saxon.expr.StaticProperty#EXACTLY_ONE}, {@link net.sf.saxon.expr.StaticProperty#ALLOWS_ONE_OR_MORE}, * {@link net.sf.saxon.expr.StaticProperty#ALLOWS_ZERO_OR_MORE} */ @Override protected int computeCardinality() { // Assume that simple cases, like a FLWOR whose clauses are all "let" clauses, will have been converted into something else. return StaticProperty.ALLOWS_ZERO_OR_MORE; } /** * Get the immediate sub-expressions of this expression. * * @return an iterator containing the sub-expressions of this expression */ /*@NotNull*/ @Override public Iterator iterateSubExpressions() { final List list = new ArrayList(5); ExpressionProcessor processor = new ExpressionProcessor() { public Expression processExpression(Expression expr) { list.add(expr); return expr; } }; try { for (Clause c : clauses) { c.processSubExpressions(processor); } } catch (XPathException e) { throw new IllegalStateException(e); } list.add(returnClause); return list.iterator(); } /** * 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 Iterator iterateSubExpressionInfo() { final List list = new ArrayList(5); ExpressionProcessor processor = new ExpressionProcessor() { public Expression processExpression(Expression expr) { list.add(new SubExpressionInfo(expr, true, false, NAVIGATION_CONTEXT)); return expr; } }; boolean foundLoopClause = false; try { for (Clause c : clauses) { if (isLoopingClause(c)) { foundLoopClause = true; } c.processSubExpressions(processor); } } catch (XPathException e) { throw new IllegalStateException(e); } list.add(new SubExpressionInfo(returnClause, true, foundLoopClause, INHERITED_CONTEXT)); return list.iterator(); } /** * Check to ensure that this expression does not contain any inappropriate updating subexpressions. * This check is overridden for those expressions that permit updating subexpressions. * * @throws net.sf.saxon.trans.XPathException * if the expression has a non-permitted updateing subexpression */ public void checkForUpdatingSubexpressions() throws XPathException { ExpressionProcessor processor = new ExpressionProcessor() { public Expression processExpression(Expression expr) throws XPathException { expr.checkForUpdatingSubexpressions(); if (expr.isUpdatingExpression()) { throw new XPathException( "An updating expression cannot be used in a clause of a FLWOR expression", "XUST0001"); } return expr; } }; for (Clause c : clauses) { c.processSubExpressions(processor); } returnClause.checkForUpdatingSubexpressions(); } /** * Determine whether this is an updating expression as defined in the XQuery update specification * * @return true if this is an updating expression */ @Override public boolean isUpdatingExpression() { return returnClause.isUpdatingExpression(); } /** * Replace one subexpression by a replacement subexpression * * @param original the original subexpression * @param replacement the replacement subexpression * @return true if the original subexpression is found */ @Override public boolean replaceSubExpression(final Expression original, final Expression replacement) { final List changed = new ArrayList(); ExpressionProcessor processor = new ExpressionProcessor() { public Expression processExpression(Expression expr) { if (expr == original) { changed.add(Boolean.TRUE); return replacement; } return expr; } }; try { for (Clause c : clauses) { c.processSubExpressions(processor); } } catch (XPathException e) { throw new IllegalStateException(e); } if (returnClause == original) { returnClause = replacement; return true; } return !changed.isEmpty(); } /** * Diagnostic print of expression structure. The abstract expression tree * is written to the supplied output destination. * * @param out the expression presenter used to display the structure */ @Override public void explain(ExpressionPresenter out) { out.startElement("FLWOR"); for (Clause c : clauses) { c.explain(out); } out.startSubsidiaryElement("return"); returnClause.explain(out); out.endSubsidiaryElement(); out.endElement(); } /** * Copy an expression. This makes a deep copy. * * @return the copy of the original expression */ /*@NotNull*/ @Override public Expression copy() { List newClauses = new ArrayList(); List oldBindings = new ArrayList(); List newBindings = new ArrayList(); for (Clause c : clauses) { Clause c2 = c.copy(); oldBindings.addAll(Arrays.asList(c.getRangeVariables())); newBindings.addAll(Arrays.asList(c2.getRangeVariables())); newClauses.add(c2); } FLWORExpression f2 = new FLWORExpression(newClauses, returnClause.copy()); ExpressionTool.copyLocationInfo(this, f2); for (int i = 0; i < oldBindings.size(); i++) { ExpressionTool.rebindVariableReferences(f2, oldBindings.get(i), newBindings.get(i)); } return f2; } /** * Promote this expression if possible */ public Expression promote(final PromotionOffer offer, final Expression parent) throws XPathException { ExpressionProcessor processor = new ExpressionProcessor() { /*@Nullable*/ public Expression processExpression(Expression expr) throws XPathException { return doPromotion(expr, offer); } }; Expression exp = offer.accept(parent, this); if (exp != null) { return exp; } else if (offer.action == PromotionOffer.RANGE_INDEPENDENT || offer.action == PromotionOffer.FOCUS_INDEPENDENT) { // Pass the offer to the action expression only if the action isn't dependent on a // variable bound within the FLWOR expression Binding[] savedBindingList = offer.bindingList; for (Clause c : clauses) { offer.bindingList = extendBindingList(offer.bindingList, c.getRangeVariables()); c.processSubExpressions(processor); } offer.bindingList = savedBindingList; return this; } else { try { for (Clause c : clauses) { c.processSubExpressions(processor); } } catch (XPathException e) { throw new IllegalStateException(e); } returnClause.promote(offer, this); return this; } } /*@Nullable*/ private Binding[] extendBindingList(/*@Nullable*/ Binding[] bindings, /*@Nullable*/ LocalVariableBinding[] moreBindings) { if (bindings == null) { bindings = new Binding[0]; } if (moreBindings == null || moreBindings.length == 0) { return bindings; } else { Binding[] b2 = new Binding[bindings.length + moreBindings.length]; System.arraycopy(bindings, 0, b2, 0, bindings.length); System.arraycopy(moreBindings, 0, b2, bindings.length, moreBindings.length); return b2; } } @Override public int getEvaluationMethod() { return Expression.PROCESS_METHOD; } /*@NotNull*/ public Expression optimize( final ExpressionVisitor visitor, final ExpressionVisitor.ContextItemType contextItemType) throws XPathException { // Optimize all the subexpressions for (Clause c : clauses) { c.processSubExpressions(new ExpressionProcessor() { public Expression processExpression(Expression expr) throws XPathException { return visitor.optimize(expr, contextItemType); } }); c.optimize(visitor, contextItemType); } // Optimize the return expression returnClause = returnClause.optimize(visitor, contextItemType); // If any 'let' clause declares a variable that is used only once, then inline it. If the variable // is not used at all, then eliminate it boolean tryAgain; boolean changed = false; do { tryAgain = false; for (Clause c : clauses) { if (c.getClauseKey() == Clause.LET) { LetClause lc = (LetClause)c; if (!ExpressionTool.dependsOnVariable(this, new Binding[]{lc.getRangeVariable()})) { clauses.remove(c); tryAgain = true; break; } boolean suppressInlining = false; for (Clause c2 : clauses) { if (c2.containsNonInlineableVariableReference(lc.getRangeVariable())) { suppressInlining = true; break; } } if (!suppressInlining) { if (lc.getRangeVariable().getNominalReferenceCount() == 1 || lc.getSequence() instanceof VariableReference || lc.getSequence() instanceof Literal) { ExpressionTool.replaceVariableReferences(this, lc.getRangeVariable(), lc.getSequence().copy()); clauses.remove(c); if (clauses.isEmpty()) { return returnClause; } tryAgain = true; break; } } } } changed |= tryAgain; } while (tryAgain); // If changed, remove any redundant trace clauses for (int i=clauses.size()-1; i>=1; i--) { if (clauses.get(i).getClauseKey() == Clause.TRACE && clauses.get(i-1).getClauseKey() == Clause.TRACE) { clauses.remove(i); } } // If any 'where' clause depends on the context item, remove this dependency, because it makes // it easier to rearrange where clauses as predicates boolean depends = false; for (Clause w : clauses) { if (w instanceof WhereClause && ExpressionTool.dependsOnFocus(((WhereClause) w).getPredicate())) { depends = true; break; } } if (depends) { Expression expr1 = ExpressionTool.tryToFactorOutDot(this, contextItemType.itemType); if (expr1 == null || expr1 == this) { //no optimisation possible return this; } resetLocalStaticProperties(); return expr1.optimize(visitor, contextItemType); } // Now convert any terms within WHERE clauses where possible into predicates on the appropriate // expression bound to a variable on a for clause. This enables the resulting filter expression // to be handled using indexing (in Saxon-EE), and it also reduces the number of items that need // to be tested against the predicate Expression expr2 = rewriteWhereClause(visitor, contextItemType); if (expr2 != null && expr2 != this) { return expr2.optimize(visitor, contextItemType); } // If the FLWOR expression consists entirely of FOR and LET clauses, convert it to a ForExpression // or LetExpression. This is largely to take advantage of existing optimizations implemented for those // expressions. boolean allForOrLetExpr = true; for (Clause c : clauses) { if (!((c instanceof ForClause) || (c instanceof LetClause))) { allForOrLetExpr = false; break; } } if (allForOrLetExpr) { return rewriteForOrLet(visitor, contextItemType); } return this; } /** * Replace this expression by an expression that returns the same result but without * regard to order * * @param retainAllNodes true if all nodes in the result must be retained; false * if duplicates can be eliminated */ @Override public Expression unordered(boolean retainAllNodes) throws XPathException { for (Clause c : clauses) { if (c instanceof ForClause && ((ForClause)c).getPositionVariable() == null) { ((ForClause)c).setSequence(((ForClause)c).getSequence().unordered(retainAllNodes)); } } returnClause = returnClause.unordered(retainAllNodes); return this; } /** * @param visitor the expression visitor * @param contextItemType the type of the context item * @return We return this expression, with WhereClauses moved up as far as possible in the list of clauses. * A Where clause cannot move above a Count clause because it changes the number of tuples in the tuple stream. * Alternatively, return null if no rewriting is possible. * @throws XPathException if the rewrite fails for any reason */ /*@Nullable*/ private Expression rewriteWhereClause(ExpressionVisitor visitor, ExpressionVisitor.ContextItemType contextItemType) throws XPathException { WhereClause whereClause; int whereIndex = 0; class WhereClauseStruct { int whereIndex = 0; WhereClause whereClause; } List whereList = new ArrayList(); for (Clause c : clauses) { if (c instanceof WhereClause) { WhereClauseStruct wStruct = new WhereClauseStruct(); wStruct.whereClause = (WhereClause) c; //keep track of whereclause from the end of the list of clauses. //We are always attempting to rewrite whereclauses from left to right, // therefore index will always be in snyc wStruct.whereIndex = clauses.size() - whereIndex; whereList.add(wStruct); } whereIndex++; } if (whereList.size() == 0) { return null; } while (!whereList.isEmpty()) { whereClause = whereList.get(0).whereClause; whereIndex = whereList.get(0).whereIndex; Expression condition = whereClause.getPredicate(); List list = new ArrayList(5); BooleanExpression.listAndComponents(condition, list); for (int i = list.size() - 1; i >= 0; i--) { Expression term = list.get(i); for (int c = clauses.size() - whereIndex - 1; c >= 0; c--) { Clause clause = clauses.get(c); Binding[] bindingList = clause.getRangeVariables(); // Find the first clause prior to the where clause that declares variables on which the // term of the where clause depends if (ExpressionTool.dependsOnVariable(term, bindingList) || clause.getClauseKey() == Clause.COUNT) { // remove this term from the where clause Expression removedExpr = list.remove(i); if (list.isEmpty()) { // the where clause has no terms left, so remove the clause clauses.remove(clauses.size() - whereIndex); } else { // change the predicate of the where clause to use only those terms that remain whereClause.setPredicate(makeAndCondition(list)); } if ((clause instanceof ForClause) && !(((ForClause) clause).isAllowingEmpty())) { // if the clause is a "for" clause, try to add the term as a predicate boolean added = ((ForClause) clause).addPredicate(this, visitor, contextItemType, term); //If we cannot add the WhereClause term as a predicate then put it back into the list of clauses if (!added) { clauses.add(c + 1, new WhereClause(removedExpr)); } } else { // the clause is not a "for" clause, so just move the "where" to this place in the list of clauses WhereClause newWhere = new WhereClause(term); clauses.add(c + 1, newWhere); } // we found a variable on which the term depends so we can't move it any further break; } } if (list.size() - 1 == i) { list.remove(i); if (list.isEmpty()) { clauses.remove(clauses.size() - whereIndex); } else { whereClause.setPredicate(makeAndCondition(list)); } WhereClause newWhere = new WhereClause(term); clauses.add(0, newWhere); } } whereList.remove(0); } return this; } /** * Recursive method to make a list of expressions into a AndExpression * * @param list of Expression * @return And Expression of list of expressions */ private Expression makeAndCondition(List list) { if (list.size() == 1) { return list.get(0); } else { return new AndExpression(list.get(0), makeAndCondition(list.subList(1, list.size()))); } } /** * Rewrite a FLWOR expression that consists entirely of "for" and "let" clauses as * a LetExpression or ForExpression * * @param visitor - ExpressionVisitor * @param contextItemType - ExpressionVisitor.ContextItemTyp * @return the rewritten expression * @throws net.sf.saxon.trans.XPathException * if an error occurs */ /*@NotNull*/ private Expression rewriteForOrLet(ExpressionVisitor visitor, ExpressionVisitor.ContextItemType contextItemType) throws XPathException { Expression action = returnClause; CodeInjector injector = null; if (visitor.getStaticContext() instanceof QueryModule) { injector = ((QueryModule)visitor.getStaticContext()).getCodeInjector(); } for (int i = clauses.size() - 1; i >= 0; i--) { if (clauses.get(i) instanceof ForClause) { ForClause forClause = (ForClause) clauses.get(i); ForExpression forExpr; if (forClause.isAllowingEmpty()) { forExpr = (ForExpression) visitor.getConfiguration().makeOuterForExpression(); } else { forExpr = new ForExpression(); } forExpr.setLocationId(forClause.getLocationId()); forExpr.setAction(action); forExpr.setSequence(forClause.getSequence()); forExpr.setVariableQName(forClause.getRangeVariable().getVariableQName()); forExpr.setRequiredType(forClause.getRangeVariable().getRequiredType()); ExpressionTool.rebindVariableReferences(action, forClause.getRangeVariable(), forExpr); if (forClause.getPositionVariable() != null) { PositionVariable posVar = new PositionVariable(); posVar.setVariableQName(forClause.getPositionVariable().getVariableQName()); ExpressionTool.rebindVariableReferences(action, forClause.getPositionVariable(), posVar); forExpr.setPositionVariable(posVar); } action = forExpr; if (injector != null) { action = injector.inject(action, visitor.getStaticContext(), Location.FOR_EXPRESSION, forExpr.getVariableQName()); } } else { LetClause letClause = (LetClause) clauses.get(i); LetExpression letExpr = new LetExpression(); letExpr.setLocationId(letClause.getLocationId()); letExpr.setAction(action); letExpr.setSequence(letClause.getSequence()); letExpr.setVariableQName(letClause.getRangeVariable().getVariableQName()); letExpr.setRequiredType(letClause.getRangeVariable().getRequiredType()); letExpr.setRefCount(letClause.getRangeVariable().getNominalReferenceCount()); ExpressionTool.rebindVariableReferences(action, letClause.getRangeVariable(), letExpr); action = letExpr; if (injector != null) { action = injector.inject(action, visitor.getStaticContext(), Location.LET_EXPRESSION, letExpr.getVariableQName()); } } } action = action.optimize(visitor, contextItemType); return action; } /** * Return an Iterator to iterate over the values of a sequence. The value of every * expression can be regarded as a sequence, so this method is supported for all * expressions. This default implementation handles iteration for expressions that * return singleton values: for non-singleton expressions, the subclass must * provide its own implementation. * * @param context supplies the context for evaluation * @return a SequenceIterator that can be used to iterate over the result * of the expression * @throws net.sf.saxon.trans.XPathException * if any dynamic error occurs evaluating the * expression */ /*@NotNull*/ @Override public SequenceIterator iterate(XPathContext context) throws XPathException { //force push mode if there is a window clause that is not the first clause // TODO: this is a temporary solution (pre 9.4 release) because WindowPull fails to call pullTuple // on the previous clause in the FLWOR expression for (int i = 1; i < clauses.size(); i++) { if (clauses.get(i).getClauseKey() == Clause.WINDOW) { Controller controller = context.getController(); SequenceReceiver saved = context.getReceiver(); SequenceOutputter seq = controller.allocateSequenceOutputter(20); seq.getPipelineConfiguration().setHostLanguage(getHostLanguage()); context.setReceiver(seq); process(context); context.setReceiver(saved); seq.close(); return seq.iterate(); } } TuplePull stream = new SingularityPull(); for (Clause c : clauses) { stream = c.getPullStream(stream, context); } return new ReturnClauseIterator(stream, this, context); } /** * Process the instruction, without returning any tail calls * * @param context The dynamic context, giving access to the current node, * the current variables, etc. * @throws XPathException if a dynamic error occurs */ @Override public void process(XPathContext context) throws XPathException { TuplePush destination = new ReturnClausePush(returnClause); for (int i = clauses.size() - 1; i >= 0; i--) { Clause c = clauses.get(i); destination = c.getPushStream(destination, context); } destination.processTuple(context); destination.close(); } /** * Evaluate an updating expression, adding the results to a Pending Update List. * The default implementation of this method, which is used for non-updating expressions, * throws an UnsupportedOperationException * * @param context the XPath dynamic evaluation context * @param pul the pending update list to which the results should be written * @throws net.sf.saxon.trans.XPathException * if evaluation fails * @throws UnsupportedOperationException if the expression is not an updating expression */ @Override public void evaluatePendingUpdates(XPathContext context, PendingUpdateList pul) throws XPathException { TuplePull stream = new SingularityPull(); for (Clause c : clauses) { stream = c.getPullStream(stream, context); } while (stream.nextTuple(context)) { returnClause.evaluatePendingUpdates(context, pul); } } /** * Display the expression as a string */ public String toString() { FastStringBuffer sb = new FastStringBuffer(FastStringBuffer.SMALL); for (Clause c : clauses) { sb.append(c.toString()); sb.append(' '); } sb.append(" return "); sb.append(returnClause.toString()); return sb.toString(); } /** * Determine whether a variable reference found within a clause of a FLWOR expression is a looping * reference, that is, whether the variable is used more than once * @param binding the variable binding, which may be bound in a clause of the same FLWOR expression, * or in some containing expression * @return true if a reference to the variable occurs within a loop relative to the binding, that is, if the * variable's value is used more than once. Note that this method only detects a loop that is due to the clauses * of this FLWOR expression itself. A loop in an inner expression or outer expression of the FLWOR expression must * be detected by the caller. */ public boolean hasLoopingVariableReference(final Binding binding) { // Determine the clause that binds the variable (if any) int bindingClause = -1; for (int i=0; i response = new ArrayList(); ExpressionProcessor checker = new ExpressionProcessor() { public Expression processExpression(Expression expr) throws XPathException { if (response.isEmpty() && ExpressionTool.dependsOnVariable(expr, new Binding[]{binding})) { response.add(true); } return expr; } }; for (int i=clauses.size()-1; i>=0; i--) { try { clauses.get(i).processSubExpressions(checker); if (!response.isEmpty()) { lastReferencingClause = i; break; } } catch (XPathException e) { assert false; } } } // If any clause between the binding clause and the last referencing clause is a looping clause, // then the variable is used within a loop for (int i=lastReferencingClause - 1; i>=bindingClause; i--) { if (isLoopingClause(clauses.get(i))) { return true; } } // otherwise there is no loop caused by the clauses of the FLWOR expression itself. return false; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy