net.sf.saxon.expr.instruct.MemoFunction Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Saxon-HE Show documentation
Show all versions of Saxon-HE Show documentation
The XSLT and XQuery Processor
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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();
}
}
}