net.sf.saxon.expr.instruct.UserFunction Maven / Gradle / Ivy
Show all versions of Saxon-HE Show documentation
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2015 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.Configuration;
import net.sf.saxon.Controller;
import net.sf.saxon.evpull.EventIterator;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.parser.*;
import net.sf.saxon.expr.sort.AtomicComparer;
import net.sf.saxon.om.*;
import net.sf.saxon.pattern.NodeTest;
import net.sf.saxon.query.Annotation;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trace.LocationKind;
import net.sf.saxon.trans.FunctionStreamability;
import net.sf.saxon.trans.SymbolicName;
import net.sf.saxon.trans.Visibility;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.iter.SingletonIterator;
import net.sf.saxon.tree.iter.UnfailingIterator;
import net.sf.saxon.type.AnyItemType;
import net.sf.saxon.type.FunctionItemType;
import net.sf.saxon.type.ItemType;
import net.sf.saxon.type.SpecificFunctionType;
import net.sf.saxon.value.EmptySequence;
import net.sf.saxon.value.SequenceType;
import java.util.Map;
/**
* This object represents the compiled form of a user-written function
* (the source can be either an XSLT stylesheet function or an XQuery function).
*
* It is assumed that type-checking, of both the arguments and the results,
* has been handled at compile time. That is, the expression supplied as the body
* of the function must be wrapped in code to check or convert the result to the
* required type, and calls on the function must be wrapped at compile time to check or
* convert the supplied arguments.
*/
public class UserFunction extends ComponentCode implements Function, ContextOriginator {
public enum Determinism {DETERMINISTIC, PROACTIVE, ELIDABLE}
;
private StructuredQName functionName; // null for an anonymous function
private boolean tailCalls = false;
// indicates that the function contains tail calls, not necessarily recursive ones.
private boolean tailRecursive = false;
// indicates that the function contains tail calls on itself
private UserFunctionParameter[] parameterDefinitions;
private SequenceType declaredResultType;
private SequenceType resultType;
protected int evaluationMode = ExpressionTool.UNDECIDED;
private boolean isUpdating = false;
private int inlineable = -1; // 0:no 1:yes -1:don't know
private Map annotationMap;
private FunctionStreamability declaredStreamability = FunctionStreamability.UNCLASSIFIED;
private Controller preallocatedController = null;
private Determinism determinism = Determinism.PROACTIVE;
/**
* Create a user-defined function (the body must be added later)
*/
public UserFunction() {
}
public int getComponentKind() {
return StandardNames.XSL_FUNCTION;
}
/**
* Set the function name
*
* @param name the function name
*/
public void setFunctionName(StructuredQName name) {
functionName = name;
}
/**
* Get the function name
*
* @return the function name, as a StructuredQName. Returns null for an anonymous function
*/
public StructuredQName getFunctionName() {
return functionName;
}
/**
* Get a description of this function for use in error messages. For named functions, the description
* is the function name (as a lexical QName). For others, it might be, for example, "inline function",
* or "partially-applied ends-with function".
*
* @return a description of the function for use in error messages
*/
public String getDescription() {
return getFunctionName().getDisplayName();
}
/**
* Get a name identifying the object of the expression, for example a function name, template name,
* variable name, key name, element name, etc. This is used only where the name is known statically.
*/
public StructuredQName getObjectName() {
return functionName;
}
public SymbolicName getSymbolicName() {
return new SymbolicName(StandardNames.XSL_FUNCTION, functionName, getArity());
}
/**
* Get the type of the function
*
* @return the function type
*/
public FunctionItemType getFunctionItemType() {
SequenceType[] argTypes = new SequenceType[parameterDefinitions.length];
for (int i = 0; i < parameterDefinitions.length; i++) {
UserFunctionParameter ufp = parameterDefinitions[i];
argTypes[i] = ufp.getRequiredType();
}
return new SpecificFunctionType(argTypes, resultType);
}
/**
* Get the roles of the arguments, for the purposes of streaming
*
* @return an array of OperandRole objects, one for each argument
*/
public OperandRole[] getOperandRoles() {
OperandRole[] roles = new OperandRole[getArity()];
OperandUsage first = null;
switch (declaredStreamability) {
case UNCLASSIFIED:
SequenceType required = getArgumentType(0);
first = OperandRole.getTypeDeterminedUsage(required.getPrimaryType());
break;
case ABSORBING:
first = OperandUsage.ABSORPTION;
break;
case INSPECTION:
first = OperandUsage.INSPECTION;
break;
case FILTER:
first = OperandUsage.TRANSMISSION;
break;
case SHALLOW_DESCENT:
first = OperandUsage.TRANSMISSION;
break;
case DEEP_DESCENT:
first = OperandUsage.TRANSMISSION;
break;
case ASCENT:
first = OperandUsage.TRANSMISSION;
break;
}
roles[0] = new OperandRole(0, first, getArgumentType(0));
for (int i = 1; i < roles.length; i++) {
SequenceType required = getArgumentType(i);
roles[i] = new OperandRole(0, OperandRole.getTypeDeterminedUsage(required.getPrimaryType()), required);
}
return roles;
}
/**
* Ask whether any of the declared arguments accept nodes without atomizing them
*
* @return true if this is the case
*/
public boolean acceptsNodesWithoutAtomization() {
for (int i = 0; i < getArity(); i++) {
ItemType type = getArgumentType(i).getPrimaryType();
if (type instanceof NodeTest || type == AnyItemType.getInstance()) {
return true;
}
}
return false;
}
/**
* Supply the controller to be used when evaluating this function. This is used when the function is dynamically loaded
* when using the load-xquery-module function.
*
* @param controller the controller to be used (which may differ from the controller used by the caller)
*/
public void setPreallocatedController(Controller controller) {
preallocatedController = controller;
}
/**
* Supply a set of annotations
*
* @param map the new set of annotations, which will replace any previous annotations on the function
*/
public void setAnnotationMap(Map map) {
this.annotationMap = map;
}
/**
* Set the determinism of the function. Corresponds to the XSLT 3.0 values new-each-time=yes|no|maybe
*
* @param determinism the determinism value for the function
*/
public void setDeterminism(Determinism determinism) {
this.determinism = determinism;
}
/**
* Get the determinism of the function. Corresponds to the XSLT 3.0 values new-each-time=yes|no|maybe
*
* @return the determinism value for the function
*/
public Determinism getDeterminism() {
return determinism;
}
/**
* Determine the preferred evaluation mode for this function
*/
public void computeEvaluationMode() {
if (tailRecursive) {
// If this function contains tail calls, we evaluate it eagerly, because
// the caller needs to know whether a tail call was returned or not: if we
// return a Closure, the tail call escapes into the wild and can reappear anywhere...
evaluationMode = ExpressionTool.eagerEvaluationMode(getBody());
} else {
evaluationMode = ExpressionTool.lazyEvaluationMode(getBody());
}
}
/**
* Ask whether the function can be inlined
*
* @return true (yes), false (no), or null (don't know)
*/
/*@Nullable*/
public Boolean isInlineable() {
if (inlineable != -1) {
return inlineable == 1;
}
if (body == null) {
// bug 2226
return null;
}
if ((body.getSpecialProperties() & StaticProperty.HAS_SIDE_EFFECTS) != 0 || tailCalls) {
// This is mainly to handle current-output-uri()
return false;
}
Component component = getDeclaringComponent();
if (component != null) {
Visibility visibility = getDeclaringComponent().getVisibility();
if (visibility == Visibility.PRIVATE || visibility == Visibility.FINAL) {
if (inlineable < 0) {
return null;
} else {
return inlineable == 1;
}
} else {
return false;
}
} else {
return null;
}
}
/**
* Say whether this function can be inlined
*
* @param inlineable true or false
*/
public void setInlineable(boolean inlineable) {
this.inlineable = inlineable ? 1 : 0;
}
/**
* Set the definitions of the declared parameters for this function
*
* @param params an array of parameter definitions
*/
public void setParameterDefinitions(UserFunctionParameter[] params) {
parameterDefinitions = params;
}
/**
* Get the definitions of the declared parameters for this function
*
* @return an array of parameter definitions
*/
public UserFunctionParameter[] getParameterDefinitions() {
return parameterDefinitions;
}
/**
* Set the declared result type of the function
*
* @param resultType the declared return type (or item()* if no explicit type was declared)
*/
public void setResultType(SequenceType resultType) {
this.declaredResultType = resultType;
this.resultType = resultType;
}
/**
* Indicate whether the function contains a tail call
*
* @param tailCalls true if the function contains a tail call (on any function)
* @param recursiveTailCalls true if the function contains a tail call (on itself)
*/
public void setTailRecursive(boolean tailCalls, boolean recursiveTailCalls) {
this.tailCalls = tailCalls;
tailRecursive = recursiveTailCalls;
}
/**
* Determine whether the function contains tail calls (on this or other functions)
*
* @return true if the function contains tail calls
*/
public boolean containsTailCalls() {
return tailCalls;
}
/**
* Determine whether the function contains a tail call, calling itself
*
* @return true if the function contains a directly-recursive tail call
*/
public boolean isTailRecursive() {
return tailRecursive;
}
/**
* Set whether this is an updating function (as defined in XQuery Update)
*
* @param isUpdating true if this is an updating function
*/
public void setUpdating(boolean isUpdating) {
this.isUpdating = isUpdating;
}
/**
* Ask whether this is an updating function (as defined in XQuery Update)
*
* @return true if this is an updating function
*/
public boolean isUpdating() {
return isUpdating;
}
/**
* Set the declared streamability (XSLT 3.0 attribute)
*
* @param streamability the declared streamability (defaults to "unclassified")
*/
public void setDeclaredStreamability(FunctionStreamability streamability) {
this.declaredStreamability = streamability == null ? FunctionStreamability.UNCLASSIFIED : streamability;
}
/**
* Get the declared streamability (XSLT 3.0 attribute)
*
* @return the declared streamability (defaults to "unclassified")
*/
public FunctionStreamability getDeclaredStreamability() {
return this.declaredStreamability;
}
/**
* Get the type of value returned by this function
*
* @return the declared result type, or the inferred result type
* if this is more precise
*/
public SequenceType getResultType() {
Visibility vis = getDeclaredVisibility();
if (resultType == SequenceType.ANY_SEQUENCE && getBody() != null &&
(vis == Visibility.FINAL || vis == Visibility.PRIVATE)) {
// see if we can infer a more precise result type. We don't do this if the function contains
// calls on further functions, to prevent infinite regress.
if (!containsUserFunctionCalls(getBody())) {
resultType = SequenceType.makeSequenceType(
getBody().getItemType(), getBody().getCardinality());
}
}
return resultType;
}
/**
* Get the declared result type
*
* @return the declared result type, or item()* if the type was not explicitly declared
*/
public SequenceType getDeclaredResultType() {
return declaredResultType;
}
/**
* Determine whether a given expression contains calls on user-defined functions
*
* @param exp the expression to be tested
* @return true if the expression contains calls to user functions.
*/
private static boolean containsUserFunctionCalls(Expression exp) {
if (exp instanceof UserFunctionCall) {
return true;
}
for (Operand o : exp.operands()) {
if (containsUserFunctionCalls(o.getChildExpression())) {
return true;
}
}
return false;
}
/**
* Get the required types of an argument to this function
*
* @param n identifies the argument in question, starting at 0
* @return a SequenceType object, indicating the required type of the argument
*/
public SequenceType getArgumentType(int n) {
return parameterDefinitions[n].getRequiredType();
}
/**
* Get the evaluation mode. The evaluation mode will be computed if this has not already been done
*
* @return the computed evaluation mode
*/
public int getEvaluationMode() {
if (evaluationMode == ExpressionTool.UNDECIDED) {
computeEvaluationMode();
}
return evaluationMode;
}
/**
* Set the evaluation mode. (Used when reloading a saved package)
*
* @param mode the evaluation mode
*/
public void setEvaluationMode(int mode) {
evaluationMode = mode;
}
/**
* Get the arity of this function
*
* @return the number of arguments
*/
public int getArity() {
return parameterDefinitions.length;
}
/**
* Ask whether this function is a memo function
*
* @return false (overridden in a subclass)
*/
public boolean isMemoFunction() {
return false;
}
public void typeCheck(ExpressionVisitor visitor) throws XPathException {
Expression exp = getBody();
ExpressionTool.resetPropertiesWithinSubtree(exp);
Expression exp2 = exp;
try {
// We've already done the typecheck of each XPath expression, but it's worth doing again at this
// level because we have more information now.
ContextItemStaticInfo info = ContextItemStaticInfo.ABSENT;
exp2 = exp.typeCheck(visitor, info);
if (resultType != null) {
RoleDiagnostic role =
new RoleDiagnostic(RoleDiagnostic.FUNCTION_RESULT,
functionName == null ? "" : functionName.getDisplayName(), 0);
role.setErrorCode(getPackageData().getHostLanguage() == Configuration.XSLT && getFunctionName() != null ? "XTTE0780" : "XPTY0004");
exp2 = TypeChecker.staticTypeCheck(exp2, resultType, false, role, visitor);
}
} catch (XPathException err) {
err.maybeSetLocation(getLocation());
throw err;
}
if (exp2 != exp) {
setBody(exp2);
}
}
/**
* Create a context for evaluating this function
*
* @param oldContext the existing context of the caller
* @return a new context which should be supplied to the call() method.
*/
public XPathContextMajor makeNewContext(XPathContext oldContext) {
XPathContextMajor c2;
if (preallocatedController == null) {
c2 = oldContext.newCleanContext();
} else {
c2 = preallocatedController.newXPathContext();
}
c2.setOrigin(this);
c2.setReceiver(oldContext.getReceiver());
c2.setTemporaryOutputState(StandardNames.XSL_FUNCTION);
c2.setCurrentOutputUri(null);
c2.setCurrentComponent(getDeclaringComponent()); // default value for the caller to override if necessary
return c2;
}
/**
* Call this function to return a value.
*
* @param context This provides the run-time context for evaluating the function. This should be created
* using {@link #makeNewContext(XPathContext)}. It must be an instance of XPathContextMajor.
* @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.
* @return a Value representing the result of the function.
* @throws net.sf.saxon.trans.XPathException if a dynamic error occurs while evaluating the function
*/
public Sequence call(XPathContext context, Sequence[] actualArgs)
throws XPathException {
if (evaluationMode == ExpressionTool.UNDECIDED) {
// should have been done at compile time
computeEvaluationMode();
}
// Otherwise evaluate the function
XPathContextMajor c2 = (XPathContextMajor) context;
c2.setStackFrame(getStackFrameMap(), actualArgs);
Sequence result;
try {
result = ExpressionTool.evaluate(getBody(), evaluationMode, c2, 1);
} catch (XPathException err) {
err.maybeSetLocation(getLocation());
throw err;
} catch (Exception err2) {
String message = "Internal error evaluating function "
+ (functionName == null ? "(unnamed)" : functionName.getDisplayName())
+ (getLineNumber() > 0 ? " at line " + getLineNumber() : "")
+ (getSystemId() != null ? " in module " + getSystemId() : "");
throw new RuntimeException(message, err2);
}
return result;
}
/**
* Call this function in "push" mode, writing the results to the current output destination.
*
* @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.
* @throws net.sf.saxon.trans.XPathException if a dynamic error occurs while evaluating the function
*/
public void process(Sequence[] actualArgs, XPathContextMajor context)
throws XPathException {
context.setStackFrame(getStackFrameMap(), actualArgs);
getBody().process(context);
}
/**
* Call this function in "pull" mode, returning the results as a sequence of PullEvents.
*
* @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 an iterator over the results of the function call
* @throws net.sf.saxon.trans.XPathException if a dynamic error occurs while evaluating the function
*/
public EventIterator iterateEvents(Sequence[] actualArgs, XPathContextMajor context)
throws XPathException {
context.setStackFrame(getStackFrameMap(), actualArgs);
return getBody().iterateEvents(context);
}
/**
* Call this function. This method allows an XQuery function to be called directly from a Java
* application. It creates the environment needed to achieve this
*
* @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 controller This provides the run-time context for evaluating the function. A Controller
* may be obtained by calling {@link net.sf.saxon.query.XQueryExpression#newController}. This may
* be used for a series of calls on functions defined in the same module as the XQueryExpression.
* @return a Value representing the result of the function.
* @throws net.sf.saxon.trans.XPathException if a dynamic error occurs while evaluating the function.
*/
public Sequence call(Sequence[] actualArgs, Controller controller) throws XPathException {
return call(controller.newXPathContext(), actualArgs);
}
/**
* Call an updating function.
*
* @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 the dynamic evaluation context
* @param pul the pending updates list, to which the function's update actions are to be added.
* @throws net.sf.saxon.trans.XPathException if a dynamic error occurs while evaluating the function.
*/
public void callUpdating(Sequence[] actualArgs, XPathContextMajor context, PendingUpdateList pul)
throws XPathException {
context.setStackFrame(getStackFrameMap(), actualArgs);
try {
getBody().evaluatePendingUpdates(context, pul);
} catch (XPathException err) {
err.maybeSetLocation(getLocation());
throw err;
}
}
/**
* Diagnostic print of expression structure. The abstract expression tree
* is written to the supplied outputstream.
*
* @param presenter the expression presenter used to display the structure
*/
@Override
public void export(ExpressionPresenter presenter) throws XPathException {
presenter.startElement("function");
if (getFunctionName() != null) {
presenter.emitAttribute("name", getFunctionName().getEQName());
presenter.emitAttribute("line", getLineNumber() + "");
presenter.emitAttribute("module", getSystemId());
presenter.emitAttribute("eval", getEvaluationMode() + "");
}
String flags = "";
if (determinism == Determinism.PROACTIVE) {
flags += "p";
} else if (determinism == Determinism.ELIDABLE) {
flags += "e";
} else {
flags += "d";
}
if (isMemoFunction()) {
flags += "m";
}
switch (declaredStreamability) {
case UNCLASSIFIED:
flags += "U";
break;
case ABSORBING:
flags += "A";
break;
case INSPECTION:
flags += "I";
break;
case FILTER:
flags += "F";
break;
case SHALLOW_DESCENT:
flags += "S";
break;
case DEEP_DESCENT:
flags += "D";
break;
case ASCENT:
flags += "C";
break;
}
presenter.emitAttribute("flags", flags);
presenter.emitAttribute("as", getDeclaredResultType().toString());
presenter.emitAttribute("slots", getStackFrameMap().getNumberOfVariables() + "");
for (UserFunctionParameter p : parameterDefinitions) {
presenter.startElement("arg");
presenter.emitAttribute("name", p.getVariableQName());
presenter.emitAttribute("as", p.getRequiredType().toString());
presenter.endElement();
}
presenter.setChildRole("body");
getBody().export(presenter);
presenter.endElement();
}
public boolean isTrustedResultType() {
return false;
}
/**
* Get the type of construct. This will either be the fingerprint of a standard XSLT instruction name
* (values in {@link net.sf.saxon.om.StandardNames}: all less than 1024)
* or it will be a constant in class {@link LocationKind}.
*/
public int getConstructType() {
return LocationKind.FUNCTION;
}
/**
* Get an iterator over all the items in the sequence (that is, the singleton sequence
* consisting of this function item)
*
* @return an iterator over all the items
*/
public UnfailingIterator iterate() {
return SingletonIterator.makeIterator(this);
}
/**
* Ask whether this function item is a map
*
* @return true if this function item is a map, otherwise false
*/
public boolean isMap() {
return false;
}
/**
* Ask whether this function item is an array
*
* @return true if this function item is an array, otherwise false
*/
public boolean isArray() {
return false;
}
/**
* Test whether this FunctionItem is deep-equal to another function item,
* under the rules of the deep-equal function
*
* @param other the other function item
* @param context the dynamic evaluation context
* @param comparer the object to perform the comparison
* @param flags options for how the comparison is performed
* @return true if the two function items are deep-equal
* @throws net.sf.saxon.trans.XPathException if the comparison cannot be performed
*/
public boolean deepEquals(Function other, XPathContext context, AtomicComparer comparer, int flags) throws XPathException {
XPathException err = new XPathException("Cannot compare functions using deep-equal", "FOTY0015");
err.setIsTypeError(true);
err.setXPathContext(context);
throw err;
}
/**
* Get the n'th item in the value, counting from 0
*
* @param n the index of the required item, with 0 representing the first item in the sequence
* @return the n'th item if it exists, or null otherwise
*/
public Item itemAt(int n) {
return n == 0 ? this : null;
}
/**
* Get a subsequence of the value
*
* @param start the index of the first item to be included in the result, counting from zero.
* A negative value is taken as zero. If the value is beyond the end of the sequence, an empty
* sequence is returned
* @param length the number of items to be included in the result. Specify Integer.MAX_VALUE to
* get the subsequence up to the end of the base sequence. If the value is negative, an empty sequence
* is returned. If the value goes off the end of the sequence, the result returns items up to the end
* of the sequence
* @return the required subsequence.
*/
public GroundedValue subsequence(int start, int length) {
return start <= 0 && (start + length) > 0 ? this : EmptySequence.getInstance();
}
/**
* Get the size of the value (the number of items)
*
* @return the number of items in the sequence
*/
public int getLength() {
return 1;
}
/**
* Get the effective boolean value of this sequence
*
* @return the effective boolean value
* @throws net.sf.saxon.trans.XPathException if the sequence has no effective boolean value (for example a sequence of two integers)
*/
public boolean effectiveBooleanValue() throws XPathException {
return ExpressionTool.effectiveBooleanValue(this);
}
/**
* Reduce the sequence to its simplest form. If the value is an empty sequence, the result will be
* EmptySequence.getInstance(). If the value is a single atomic value, the result will be an instance
* of AtomicValue. If the value is a single item of any other kind, the result will be an instance
* of One. Otherwise, the result will typically be unchanged.
*
* @return the simplified sequence
*/
public GroundedValue reduce() {
return this;
}
/**
* Get the first item in the sequence. Differs from the superclass {@link net.sf.saxon.om.Sequence} in that
* no exception is thrown.
*
* @return the first item in the sequence if there is one, or null if the sequence
* is empty
*/
public Item head() {
return this;
}
/**
* Get the value of the item as a string. For nodes, this is the string value of the
* node as defined in the XPath 2.0 data model, except that all nodes are treated as being
* untyped: it is not an error to get the string value of a node with a complex type.
* For atomic values, the method returns the result of casting the atomic value to a string.
*
* If the calling code can handle any CharSequence, the method {@link #getStringValueCS} should
* be used. If the caller requires a string, this method is preferred.
*
* @return the string value of the item
* @throws UnsupportedOperationException if the item is a function item (an unchecked exception
* is used here to avoid introducing exception handling to a large number of paths where it is not
* needed)
* @see #getStringValueCS
* @since 8.4
*/
public String getStringValue() {
throw new UnsupportedOperationException("A function has no string value");
}
/**
* Get the string value of the item as a CharSequence. This is in some cases more efficient than
* the version of the method that returns a String. The method satisfies the rule that
* X.getStringValueCS().toString()
returns a string that is equal to
* X.getStringValue()
.
*
* Note that two CharSequence values of different types should not be compared using equals(), and
* for the same reason they should not be used as a key in a hash table.
*
* If the calling code can handle any CharSequence, this method should
* be used. If the caller requires a string, the {@link #getStringValue} method is preferred.
*
* @return the string value of the item
* @throws UnsupportedOperationException if the item is a function item (an unchecked exception
* is used here to avoid introducing exception handling to a large number of paths where it is not
* needed)
* @see #getStringValue
* @since 8.4
*/
public CharSequence getStringValueCS() {
return getStringValue();
}
/**
* Atomize the item.
*
* @return the result of atomization
* @throws net.sf.saxon.trans.XPathException if atomization is not allowed
* for this kind of item
*/
public AtomicSequence atomize() throws XPathException {
throw new XPathException("Functions cannot be atomized", "FOTY0013");
}
}