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

net.sf.saxon.expr.accum.AccumulatorData Maven / Gradle / Ivy

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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.accum;

import net.sf.saxon.Controller;
import net.sf.saxon.expr.Expression;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.expr.XPathContextMajor;
import net.sf.saxon.expr.instruct.SlotManager;
import net.sf.saxon.expr.parser.ExpressionTool;
import net.sf.saxon.om.*;
import net.sf.saxon.trans.Err;
import net.sf.saxon.trans.UncheckedXPathException;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.trans.rules.Rule;
import net.sf.saxon.tree.iter.ManualIterator;
import net.sf.saxon.tree.util.Navigator;

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

/**
 * Holds the values of an accumulator function for one non-streamed document
 */
public class AccumulatorData implements IAccumulatorData {

    private final Accumulator accumulator;
    private final List values = new ArrayList<>();
    private boolean building = false;

    public AccumulatorData(Accumulator acc) {
        this.accumulator = acc;
    }

    /**
     * Get the associated accumulator
     * @return the accumulator
     */

    @Override
    public Accumulator getAccumulator() {
        return accumulator;
    }

    /**
     * Build a data structure containing the values of the accumulator for each node in a document.
     * The data structure holds the value for all nodes where the value changes; the value for other
     * nodes is obtained by interpolation
     *
     * @param doc     the root of the tree for which the accumulator is to be evaluated
     * @param context the dynamic evaluation context
     * @throws XPathException if a dynamic error occurs while evaluating the accumulator
     */

    public void buildIndex(NodeInfo doc, XPathContext context) throws XPathException {
        //System.err.println("Building accData " + this);
        try {
            if (building) {
                throw new XPathException("Accumulator " + accumulator.getAccumulatorName().getDisplayName() +
                    " requires access to its own value", "XTDE3400");
            }
            building = true;
            Expression initialValue = accumulator.getInitialValueExpression();
            XPathContextMajor c2 = context.newContext();
            SlotManager sf = accumulator.getSlotManagerForInitialValueExpression();
            Sequence[] slots = new Sequence[sf.getNumberOfVariables()];
            c2.setStackFrame(sf, slots);
            c2.setCurrentIterator(new ManualIterator(doc));
            Sequence val = SequenceTool.toGroundedValue(initialValue.iterate(c2));
            values.add(new DataPoint(new Visit(doc, false), val));
            val = visit(doc, val, c2);
            values.add(new DataPoint(new Visit(doc, true), val));
            ((ArrayList) values).trimToSize();
            building = false;
        } catch (UncheckedXPathException e) {
            throw e.getXPathException();
        }
        //diagnosticPrint();
    }

    /*
     * Diagnostic output of the entire data structure
     */

//    public void diagnosticPrint() {
//        for (DataPoint dp : values) {
//            System.err.println((dp.visit.isPostDescent ? "B:" : "A:") + Navigator.getPath(dp.visit.node) + " = " + dp.value);
//        }
//    }

    /**
     * Recursive routine to evaluate the accumulator for a given node, before and after
     * visiting its descendants
     *
     * @param node    the node to be visited
     * @param value   the value of the accumulator before visiting this node
     * @param context the dynamic evaluation context
     * @return the value of the accumulator after visiting this node
     * @throws XPathException if a dynamic evaluation error occurs
     */

    @SuppressWarnings({"InfiniteRecursion"}) //Spurious warning from IntelliJ
    private Sequence visit(NodeInfo node, Sequence value, XPathContext context) throws XPathException {
        try {
            ((ManualIterator)context.getCurrentIterator()).setContextItem(node);
            Rule rule = accumulator.getPreDescentRules().getRule(node, context);
            if (rule != null) {
                value = processRule(rule, node, false, value, context);
                logChange(node, value, context, " BEFORE ");
            }
            for (NodeInfo kid : node.children()) {
                value = visit(kid, value, context);
            }
            ((ManualIterator) context.getCurrentIterator()).setContextItem(node);
            rule = accumulator.getPostDescentRules().getRule(node, context);
            if (rule != null) {
                value = processRule(rule, node, true, value, context);
                logChange(node, value, context, " AFTER ");
            }
            return value;
        } catch (StackOverflowError e) {
            throw new XPathException.StackOverflow(
                    "Too many nested accumulator evaluations. The accumulator definition may have cyclic dependencies",
                    "XTDE3400", accumulator)
                    .withXPathContext(context);
        }
    }

    private void logChange(NodeInfo node, Sequence value, XPathContext context, String phase) {
        if (accumulator.isTracing()) {
            context.getConfiguration().getLogger().info(
                    accumulator.getAccumulatorName().getDisplayName()
                            + phase + Navigator.getPath(node) + ": " + Err.depictSequence(value));
        }
    }

    /**
     * Apply an accumulator rule
     *
     * @param rule    the rule to apply
     * @param node    the node that was matched
     * @param isPostDescent false for the pre-descent visit to a node, true for the post-descent visit
     * @param value   the value of the accumulator before applying the rule
     * @param context the dynamic evaluation context
     * @return the value of the accumulator after applying the rule
     * @throws XPathException if a dynamic error occurs during the evaluation
     */

    private Sequence processRule(Rule rule, NodeInfo node, boolean isPostDescent, Sequence value, XPathContext context) throws XPathException {
        AccumulatorRule target = (AccumulatorRule) rule.getAction();
        Expression delta = target.getNewValueExpression();
        XPathContextMajor c2 = context.newCleanContext();
        final Controller controller = c2.getController();
        assert controller != null;
        ManualIterator initialNode = new ManualIterator(node);
        c2.setCurrentIterator(initialNode);
        c2.openStackFrame(target.getStackFrameMap());
        c2.setLocalVariable(0, value);
        c2.setCurrentComponent(accumulator.getDeclaringComponent());
        c2.setTemporaryOutputState(StandardNames.XSL_ACCUMULATOR_RULE);
        value = ExpressionTool.eagerEvaluate(delta, c2);
        //System.err.println("Node " + ((TinyNodeImpl) node).getNodeNumber() + " : " + value);
        if (node.getParent() == null && !isPostDescent && values.size() == 1) {
            // Overwrite the accumulator's initial value with the "before document start" value. Bug 4786.
            values.clear();
        }
        values.add(new DataPoint(new Visit(node, isPostDescent), value));
        return value;
    }

    /**
     * Get the value of the accumulator for a given node
     *
     * @param node        the node in question
     * @param postDescent false if the pre-descent value of the accumulator is required;
     *                    false if the post-descent value is wanted.
     * @return the value of the accumulator for this node
     */

//    public Sequence getValue(NodeInfo node, boolean postDescent) {
//        Visit visit = new Visit(node, postDescent);
//        return search(0, values.size(), visit);
//    }

    @Override
    public Sequence getValue(NodeInfo node, boolean postDescent) {
        Visit visit = new Visit(node, postDescent);
        return search(0, values.size(), visit);
        //System.err.println("Searched " + values.size() + " " + ((TinyNodeImpl) visit.node).getNodeNumber() + " : " + seq);
    }

    /**
     * Recursive binary chop search for the value applicable to a given node
     *
     * @param start  the start index of the search
     * @param end    the end index of the search
     * @param sought the visit being sought
     * @return the value associated with this node
     */

    private Sequence search(int start, int end, Visit sought) {
        //System.err.println("-- Search " + start + ".." + end);
        if (start == end) {
            // sometimes we want the value for the visit we've found, sometimes for the previous visit
            int rel = sought.compareTo(values.get(start).visit);
            if (rel < 0 /*|| (rel == 0 && sought.isPostDescent)*/) {
                return values.get(start - 1).value;
            } else {
                return values.get(start).value;
            }
        }
        int mid = (start + end) / 2;
        if (sought.compareTo(values.get(mid).visit) <= 0) {
            return search(start, mid, sought);
        } else {
            return search(mid + 1, end, sought);
        }

        // 9.6:
//        int mid = (start + end) / 2;
//        if (sought.compareTo(values.get(mid).visit) <= 0) {
//            return search(start, mid, sought);
//        } else {
//            return search(mid + 1, end, sought);
//        }
    }

    /**
     * Class representing one of the two visits to a node during a tree-walk
     */

    private static class Visit implements Comparable {
        public NodeInfo node;
        public boolean isPostDescent;

        public Visit(NodeInfo node, boolean isPostDescent) {
            this.node = node;
            this.isPostDescent = isPostDescent;
        }

        /**
         * Compare the order of two node visits.
         *
         * @param other the other node visit
         * @return -1 if this visit is earlier, 0 if they are the same visit, +1 if this visit is later
         */

        @Override
        public int compareTo(Visit other) {
            int relation = Navigator.comparePosition(node, other.node);
            switch (relation) {
                case AxisInfo.SELF:
                    if (isPostDescent == other.isPostDescent) {
                        return 0;
                    } else {
                        return isPostDescent ? +1 : -1;
                    }
                case AxisInfo.PRECEDING:
                    return -1;
                case AxisInfo.FOLLOWING:
                    return +1;
                case AxisInfo.ANCESTOR:
                    return isPostDescent ? +1 : -1;
                case AxisInfo.DESCENDANT:
                    return other.isPostDescent ? -1 : +1;
                default:
                    throw new IllegalStateException();
            }
        }
    }

    /**
     * Class representing a value of the accumulator immediately after a particular visit to a node.
     */

    private static class DataPoint {
        public Visit visit;
        public Sequence value;

        public DataPoint(Visit visit, Sequence value) {
            this.visit = visit;
            this.value = value;
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy