Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions exist-core/src/main/java/org/exist/xquery/Function.java
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,37 @@ public boolean isCalledAs(final String localName) {
return localName.equals(mySignature.getName().getLocalPart());
}

/**
* Indicates whether this function's behaviour depends on the dynamic
* context (focus, current dateTime, default collation, static base URI,
* implicit timezone, ...).
* <p>
* Used by {@link FunctionFactory#wrap(XQueryContext, Function)} to decide
* whether the lifted wrapper around a built-in must forward the caller's
* focus into the wrapped body when the built-in is used as a function
* item -- e.g. when retrieved via {@code fn:function-lookup} or as a
* named function reference. F&amp;O 3.1 section 16.1.1 requires the
* static and dynamic context of the lookup site to form part of the
* closure of the returned function.
* <p>
* Default is {@code false}. Overridden to {@code true} in each
* context-dependent built-in (the 0-arg forms of {@code fn:position},
* {@code fn:last}, {@code fn:node-name}, {@code fn:string},
* {@code fn:data}, {@code fn:base-uri}, {@code fn:string-length},
* {@code fn:normalize-space}, {@code fn:has-children},
* {@code fn:document-uri}, {@code fn:nilled}; the focus-using forms of
* {@code fn:lang}, {@code fn:id}, {@code fn:idref},
* {@code fn:element-with-id}; and {@code fn:current-dateTime},
* {@code fn:current-date}, {@code fn:current-time},
* {@code fn:implicit-timezone}, {@code fn:default-collation},
* {@code fn:default-language}, {@code fn:static-base-uri}).
*
* @return {@code true} if this function depends on the dynamic context
*/
public boolean isContextDependent() {
return false;
}

@Override
public int getDependencies() {
return Dependency.CONTEXT_ITEM | Dependency.CONTEXT_SET;
Expand Down
14 changes: 14 additions & 0 deletions exist-core/src/main/java/org/exist/xquery/FunctionFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,20 @@ public static FunctionCall wrap(XQueryContext context, Function call) throws XPa
newSignature.setArgumentTypes(newParamArray);

final UserDefinedFunction func = new UserDefinedFunction(context, newSignature);
// This wrapper exists to lift a built-in Function into a FunctionCall
// so that it can be used as a function item. Per F&O 3.1 section
// 16.1.1, the static and dynamic context of the call to
// fn:function-lookup -- and of named function references -- forms
// part of the closure of the returned function. When the wrapped
// built-in is itself context-dependent (fn:position#0,
// fn:node-name#0, fn:lang#1, fn:default-collation,
// fn:static-base-uri, ...), the wrapper must forward that captured
// focus into the body. Each Function subclass declares its own
// context-dependency via Function.isContextDependent(); the default
// is false, so non-context-dependent built-ins (fn:concat,
// fn:string-length#1, ...) and user-defined functions do not pay
// the propagation cost.
func.setPropagateContextToBody(call.isContextDependent());
for (final QName varName: variables) {
func.addVariable(varName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ public int getArgumentCount() {
return function.getArgumentCount();
}

@Override
public boolean isContextDependent() {
return function.isContextDependent();
}

@Override
public QName getName() {
return function.getName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ public class UserDefinedFunction extends Function implements Cloneable {
private boolean hasBeenReset = false;
private List<ClosureVariable> closureVariables = null;

// When true, propagate the caller's focus (context sequence + item) to the
// body. Set by FunctionFactory.wrap so that named-function-reference and
// fn:function-lookup wrappers around context-dependent built-ins
// (fn:node-name#0, fn:string#0, fn:position#0, fn:lang#1, ...) can see a
// focus. Plain user-defined functions are not supposed to depend on the
// caller's focus, so this flag stays false for them. See F&O 3.1 §16.1.1.
private boolean propagateContextToBody = false;

public void setPropagateContextToBody(final boolean propagateContextToBody) {
this.propagateContextToBody = propagateContextToBody;
}

public UserDefinedFunction(XQueryContext context, FunctionSignature signature) {
super(context, signature);
}
Expand Down Expand Up @@ -155,7 +167,11 @@ public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathExc
", got " + currentArguments[j].getItemCount());
}
}
result = body.eval(null, null);
if (propagateContextToBody) {
result = body.eval(contextSequence, contextItem);
} else {
result = body.eval(null, null);
}
return result;
} finally {
// restore the local variable stack
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ public FnDefaultLanguage(final XQueryContext context) {
super(context, FS_DEFAULT_LANGUAGE);
}

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

@Override
public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public FnHasChildren(final XQueryContext context, final FunctionSignature signat
super(context, signature);
}

@Override
public boolean isContextDependent() {
return getArgumentCount() == 0;
}

@Override
public Sequence eval(Sequence contextSequence, final Item contextItem) throws XPathException {
final NodeValue node;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ public FunBaseURI(XQueryContext context, FunctionSignature signature) {
super(context, signature);
}

@Override
public boolean isContextDependent() {
// 0-arg base-uri() reads the focus; static-base-uri() reads the
// call-site's static base URI from the XQueryContext. Both forms
// must close over the lookup-site context when wrapped via
// fn:function-lookup or a named function reference.
return isCalledAs(FS_STATIC_BASE_URI) || getArgumentCount() == 0;
}

/* (non-Javadoc)
* @see org.exist.xquery.BasicFunction#eval(org.exist.xquery.value.Sequence[],
* org.exist.xquery.value.Sequence)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ public FunCurrentDateTime(XQueryContext context, FunctionSignature signature) {
super(context, signature);
}

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

public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException {
if (context.getProfiler().isEnabled()) {
context.getProfiler().start(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ public FunData(XQueryContext context, FunctionSignature signature) {
super(context, signature);
}

@Override
public boolean isContextDependent() {
return getArgumentCount() == 0;
}

/* (non-Javadoc)
* @see org.exist.xquery.Expression#eval(org.exist.xquery.value.Sequence,
* org.exist.xquery.value.Item)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ public FunDefaultCollation(XQueryContext context) {
super(context, signature);
}

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

public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException {
if (context.getProfiler().isEnabled()) {
context.getProfiler().start(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ public FunDocumentURI(final XQueryContext context, final FunctionSignature signa
super(context, signature);
}

@Override
public boolean isContextDependent() {
return getArgumentCount() == 0;
}

@Override
public Sequence eval(final Sequence contextSequence, final Item contextItem) throws XPathException {
if (context.getProfiler().isEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ public FunElementWithId(final XQueryContext context, final FunctionSignature sig
super(context, signature);
}

@Override
public boolean isContextDependent() {
return getArgumentCount() == 1;
}

@Override
public Sequence eval(final Sequence[] args, Sequence contextSequence) throws XPathException {
if (getArgumentCount() < 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ public FunGenerateId(final XQueryContext context, final FunctionSignature signat
super(context, signature);
}

@Override
public boolean isContextDependent() {
return getArgumentCount() == 0;
}

@Override
public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException {
final NodeValue node;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ public FunId(XQueryContext context, FunctionSignature signature) {
super(context, signature);
}

@Override
public boolean isContextDependent() {
return getArgumentCount() == 1;
}

/**
* @see org.exist.xquery.Expression#eval(Sequence, Item)
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ public FunIdRef(XQueryContext context, FunctionSignature signature) {
super(context, signature);
}

@Override
public boolean isContextDependent() {
return getArgumentCount() == 1;
}

/**
* @see org.exist.xquery.Expression#eval(Sequence, Item)
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ public FunImplicitTimezone(XQueryContext context) {
super(context, signature);
}

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

public Sequence eval(Sequence contextSequence, Item contextItem)
throws XPathException {
if (context.getProfiler().isEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ public FunLang(XQueryContext context, FunctionSignature signature) {
super(context, signature);
}

@Override
public boolean isContextDependent() {
return getArgumentCount() == 1;
}

public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
super.analyze(contextInfo);
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ public int getDependencies() {
Dependency.CONTEXT_POSITION;
}

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

/* (non-Javadoc)
* @see org.exist.xquery.functions.Function#eval(org.exist.xquery.StaticContext, org.exist.dom.persistent.DocumentSet, org.exist.xquery.value.Sequence, org.exist.xquery.value.Item)
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ public FunLocalName(final XQueryContext context, final FunctionSignature signatu
super(context, signature);
}

@Override
public boolean isContextDependent() {
return getArgumentCount() == 0;
}

@Override
public Sequence eval(Sequence contextSequence, final Item contextItem) throws XPathException {
if (context.getProfiler().isEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ public FunName(final XQueryContext context, final FunctionSignature signature) {
super(context, signature);
}

@Override
public boolean isContextDependent() {
return getArgumentCount() == 0;
}

@Override
public Sequence eval(Sequence contextSequence, final Item contextItem) throws XPathException {
if (context.getProfiler().isEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ public FunNamespaceURI(final XQueryContext context, final FunctionSignature sign
super(context, signature);
}

@Override
public boolean isContextDependent() {
return getArgumentCount() == 0;
}

@Override
public Sequence eval(Sequence contextSequence, final Item contextItem) throws XPathException {
if (context.getProfiler().isEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ public FunNilled(final XQueryContext context, final FunctionSignature signature)
super(context, signature);
}

@Override
public boolean isContextDependent() {
return getArgumentCount() == 0;
}

@Override
public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ public FunNodeName(final XQueryContext context, final FunctionSignature signatur
super(context, signature);
}

@Override
public boolean isContextDependent() {
return getArgumentCount() == 0;
}

@Override
public Sequence eval(Sequence contextSequence, final Item contextItem) throws XPathException {
if (context.getProfiler().isEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ public FunNormalizeSpace(final XQueryContext context, final FunctionSignature si
super(context, signature);
}

@Override
public boolean isContextDependent() {
return getArgumentCount() == 0;
}

@Override
public int returnsType() {
return Type.STRING;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ public FunNumber(XQueryContext context, FunctionSignature signature) {
super(context, signature);
}

@Override
public boolean isContextDependent() {
return getArgumentCount() == 0;
}

@Override
public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException {
if(context.getProfiler().isEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,21 @@ public Sequence eval(Sequence[] args, Sequence contextSequence)
}
throw e;
}
return call == null ? Sequence.EMPTY_SEQUENCE : new FunctionReference(this, call);
if (call == null) {
return Sequence.EMPTY_SEQUENCE;
}
final FunctionReference ref = new FunctionReference(this, call);
// F&O 3.1 section 16.1.1: the static and dynamic context of the
// call to fn:function-lookup forms part of the closure of the
// returned function. Capture the focus so that context-dependent
// built-ins (fn:position#0, fn:node-name#0, fn:lang#1, ...) run
// against the focus that was in scope at the lookup site.
final org.exist.xquery.value.Item capturedItem =
(contextSequence != null && !contextSequence.isEmpty())
? contextSequence.itemAt(0)
: null;
ref.setCapturedContext(contextSequence, capturedItem);
return ref;
} else if (isCalledAs("function-name")) {
final FunctionReference ref = (FunctionReference) args[0].itemAt(0);
final QName qname = ref.getSignature().getName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ public FunPath(final XQueryContext context, final FunctionSignature signature) {
super(context, signature);
}

@Override
public boolean isContextDependent() {
return getArgumentCount() == 0;
}

@Override
public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException {
final Sequence result;
Expand Down
Loading