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

net.sf.saxon.expr.instruct.MemoFunction Maven / Gradle / Ivy

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2020 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.instruct;

import net.sf.saxon.Controller;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.om.*;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.tiny.TinyAttributeImpl;
import net.sf.saxon.tree.tiny.TinyNodeImpl;
import net.sf.saxon.type.UType;
import net.sf.saxon.value.*;
import net.sf.saxon.z.IntHashMap;

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

/**
 * A user-defined function that is declared as a memo function, meaning that it remembers results
 * of previous calls.
 */

public class MemoFunction extends UserFunction {

    private boolean lookForNodes = false;  // true if the function signature allows nodes within argument values

    @Override
    public void setParameterDefinitions(UserFunctionParameter[] params) {
        super.setParameterDefinitions(params);
        for (UserFunctionParameter param : params) {
            if (param.getRequiredType().getPrimaryType().getUType().overlaps(UType.ANY_NODE)) {
                lookForNodes = true;
            }
        }
    }

    /**
     * Determine the preferred evaluation mode for this function
     */

    @Override
    public void computeEvaluationMode() {
        bodyEvaluator = getBody().makeElaborator().eagerly();
    }

    /**
     * Ask whether this function is a memo function
     *
     * @return true if this function is marked as a memo function
     */

    @Override
    public boolean isMemoFunction() {
        return true;
    }

    /**
     * Call this function to return a value.
     *
     * @param actualArgs the arguments supplied to the function. These must have the correct
     *                   types required by the function signature (it is the caller's responsibility to check this).
     *                   It is acceptable to supply a {@link net.sf.saxon.value.Closure} to represent a value whose
     *                   evaluation will be delayed until it is needed. The array must be the correct size to match
     *                   the number of arguments: again, it is the caller's responsibility to check this.
     * @param context    This provides the run-time context for evaluating the function. It is the caller's
     *                   responsibility to allocate a "clean" context for the function to use; the context that is provided
     *                   will be overwritten by the function.
     * @return a Value representing the result of the function.
     */

    @Override
    public Sequence call(XPathContext context, Sequence[] actualArgs) throws XPathException {

        // Ensure the arguments are all grounded
        for (int i=0; i> cacheMap = new IntHashMap<>();


        public MemoFunctionCache(boolean lookForNodes) {
            this.lookForNodes = lookForNodes;
        }


        /**
         * Compute a hash key for a set of argument values
         * @param args the set of argument values. Although supplied as type {@code Sequence[]},
         *             the members of the array must all be {@link GroundedValue} instances.
         * @return the corresponding hash code
         */
        public int hash(Sequence[] args) {
            int h = 0x389247ab;
            for (Sequence arg : args) {
                GroundedValue val = (GroundedValue) arg;
                if (val instanceof Item) {
                    h ^= hashItem((Item) val) + 1;
                } else {
                    for (Item it : val.asIterable()) {
                        h ^= hashItem(it) + 1;
                    }
                }
            }
            return h;
        }

        private int hashItem(Item it) {
            // The hash code of atomic values does not reflect the need for a stronger equality check than
            // normal XPath equality. But the distinctions, for example the fact that type annotations must be
            // equal, don't really matter; hash codes aren't unique anyway
            return it.hashCode();
        }

        /**
         * Get the cached result of the function for a given set of argument values, if available
         * @param hash the computed hash code for this set of argument values
         * @param args the argument values (which must all be instances of {@code GroundedValue})
         * @return the cached function result, if available, or null otherwise
         */
        public GroundedValue get(int hash, Sequence[] args) {
            int arity = args.length;
            List bucket = cacheMap.get(hash);
            if (bucket == null) {
                return null;
            }
            for (int i=0; i bucket = cacheMap.get(hash);
            if (bucket == null) {
                bucket = new ArrayList<>(args.length + 1);
                cacheMap.put(hash, bucket);
            }

            // Any node in any GroundedValue is replaced by a surrogate value.
            // This is to ensure that the presence of a node in the cache doesn't prevent garbage collection
            // when the node is no longer needed. (However, its surrogate will remain in the cache)

            int initialSize = bucket.size();
            for (Sequence val : args) {
                GroundedValue gVal = (GroundedValue)val;
                if (gVal instanceof AtomicValue || gVal instanceof FunctionItem || gVal instanceof EmptySequence) {
                    bucket.add(gVal);
                } else if (gVal instanceof NodeInfo) {
                    Item subs = substitute((NodeInfo)gVal);
                    if (subs == null) {
                        return; // Value is not cacheable, e.g. a streamed or mutable node
                    }
                    bucket.add(subs);
                } else if (lookForNodes) {
                    SequenceIterator iter = gVal.iterate();
                    Item it;
                    List newSeq = new ArrayList<>(gVal.getLength());

                    while ((it = iter.next()) != null) {
                        if (it instanceof NodeInfo) {
                            Item subs = substitute((NodeInfo) it);
                            if (subs == null) {
                                // Value is not cacheable, e.g. a streamed or mutable node
                                while (bucket.size() > initialSize) {
                                    bucket.remove(bucket.size() - 1);
                                }
                                return;
                            }
                            newSeq.add(subs);
                        } else {
                            newSeq.add(it);
                        }
                    }
                    bucket.add(SequenceExtent.makeSequenceExtent(newSeq));
                } else {
                    bucket.add(gVal);
                }
            }
            bucket.add(result);
        }
    }

    private static Item substitute(NodeInfo node) {
        // Surrogates are only needed to ensure that temporary nodes are eligible
        // for garbage collection. We therefore don't use them for lasting nodes
        // (nodes that are likely to remain in memory for the duration of a transformation)
        switch (node.getTreeInfo().getDurability()) {
            case LASTING:
                //Instrumentation.count("Durable nodes");
                return node;
            case TEMPORARY:
                //Instrumentation.count("Temporary nodes");
                return new NodeSurrogate(node);
            default:
                return null;
        }
    }

    /**
     * A NodeSurrogate is a value that represents a node; it encapsulates a Java function that
     * can be called to test whether a particular node is the one that it represents.
     */

    public static class NodeSurrogate extends ObjectValue> {

        protected NodeSurrogate(NodeInfo node) {
            super(matcher(node));
        }

        private static java.util.function.Function matcher(NodeInfo node) {
            if (node instanceof TinyAttributeImpl) {
                final long docNr = node.getTreeInfo().getDocumentNumber();
                final int nodeNr = ((TinyNodeImpl) node).getNodeNumber();
                return node1 -> node1 instanceof TinyAttributeImpl
                        && docNr == node1.getTreeInfo().getDocumentNumber()
                        && nodeNr == ((TinyNodeImpl) node1).getNodeNumber();
            } else if (node instanceof TinyNodeImpl) {
                final long docNr = node.getTreeInfo().getDocumentNumber();
                final int nodeNr = ((TinyNodeImpl) node).getNodeNumber();
                return node1 -> node1 instanceof TinyNodeImpl
                        && docNr == node1.getTreeInfo().getDocumentNumber()
                        && nodeNr == ((TinyNodeImpl) node1).getNodeNumber();
            } else {
                final String generatedId = generateId(node);
                return node1 -> generatedId.equals(generateId(node1));
            }
        }

        private static String generateId(NodeInfo node) {
            StringBuilder sb = new StringBuilder();
            node.generateId(sb);
            return sb.toString();
        }

    }

}







© 2015 - 2024 Weber Informatics LLC | Privacy Policy