YANG XPath functions - unit tests and bugfix
[yangtools.git] / yang / yang-data-jaxen / src / main / java / org / opendaylight / yangtools / yang / data / jaxen / YangFunctionContext.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.yang.data.jaxen;
9
10 import com.google.common.base.Optional;
11 import com.google.common.base.Preconditions;
12 import com.google.common.base.Splitter;
13 import com.google.common.base.Verify;
14 import java.util.ArrayDeque;
15 import java.util.Deque;
16 import java.util.HashSet;
17 import java.util.Iterator;
18 import java.util.List;
19 import java.util.Set;
20 import org.jaxen.ContextSupport;
21 import org.jaxen.Function;
22 import org.jaxen.FunctionCallException;
23 import org.jaxen.FunctionContext;
24 import org.jaxen.JaxenRuntimeException;
25 import org.jaxen.UnresolvableException;
26 import org.jaxen.UnsupportedAxisException;
27 import org.jaxen.XPathFunctionContext;
28 import org.opendaylight.yangtools.yang.common.QName;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
31 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
38 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.Module;
40 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
41 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
42 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
43 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
45 import org.opendaylight.yangtools.yang.model.api.TypedSchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
47 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
48 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
49 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
50 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
51 import org.opendaylight.yangtools.yang.model.util.RegexUtils;
52 import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
53
54 /**
55  * A {@link FunctionContext} which contains also YANG-specific functions current(), re-match(), deref(),
56  * derived-from(), derived-from-or-self(), enum-value() and bit-is-set().
57  */
58 final class YangFunctionContext implements FunctionContext {
59     private static final Splitter COLON_SPLITTER = Splitter.on(':');
60     private static final Double DOUBLE_NAN = Double.NaN;
61
62     // Core XPath functions, as per http://tools.ietf.org/html/rfc6020#section-6.4.1
63     private static final FunctionContext XPATH_FUNCTION_CONTEXT = new XPathFunctionContext(false);
64     // current() function, as per http://tools.ietf.org/html/rfc6020#section-6.4.1
65     private static final Function CURRENT_FUNCTION = (context, args) -> {
66         if (!args.isEmpty()) {
67             throw new FunctionCallException("current() takes no arguments.");
68         }
69
70         Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
71         return ((NormalizedNodeContext) context);
72     };
73
74     // re-match(string subject, string pattern) function as per https://tools.ietf.org/html/rfc7950#section-10.2.1
75     private static final Function REMATCH_FUNCTION = (context, args) -> {
76         if (args == null || args.size() != 2) {
77             throw new FunctionCallException("re-match() takes two arguments: string subject, string pattern.");
78         }
79
80         if (!(args.get(0) instanceof String)) {
81             throw new FunctionCallException("First argument of re-match() should be a String.");
82         }
83
84         if (!(args.get(1) instanceof String)) {
85             throw new FunctionCallException("Second argument of re-match() should be a String.");
86         }
87
88         final String subject = (String) args.get(0);
89         final String rawPattern = (String) args.get(1);
90
91         final String pattern = RegexUtils.getJavaRegexFromXSD(rawPattern);
92
93         return (Boolean) subject.matches(pattern);
94     };
95
96     // deref(node-set nodes) function as per https://tools.ietf.org/html/rfc7950#section-10.3.1
97     private static final Function DEREF_FUNCTION = (context, args) -> {
98         if (!args.isEmpty()) {
99             throw new FunctionCallException("deref() takes only one argument: node-set nodes.");
100         }
101
102         Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
103
104         final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
105         final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
106         final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext, currentNodeContext);
107
108         final Object nodeValue = currentNodeContext.getNode().getValue();
109
110         if (correspondingSchemaNode.getType() instanceof InstanceIdentifierTypeDefinition
111                 && nodeValue instanceof YangInstanceIdentifier) {
112             return getNodeReferencedByInstanceIdentifier((YangInstanceIdentifier) nodeValue, currentNodeContext);
113         }
114
115         if (correspondingSchemaNode.getType() instanceof LeafrefTypeDefinition) {
116             final LeafrefTypeDefinition leafrefType = (LeafrefTypeDefinition) correspondingSchemaNode.getType();
117             final RevisionAwareXPath xPath = leafrefType.getPathStatement();
118             return getNodeReferencedByLeafref(xPath, currentNodeContext, schemaContext, correspondingSchemaNode, nodeValue);
119         }
120
121         return null;
122     };
123
124     private static NormalizedNode<?, ?> getNodeReferencedByInstanceIdentifier(final YangInstanceIdentifier path,
125             final NormalizedNodeContext currentNodeContext) {
126         final NormalizedNodeNavigator navigator = (NormalizedNodeNavigator) currentNodeContext.getNavigator();
127         final NormalizedNode<?, ?> rootNode = navigator.getRootNode();
128         final List<PathArgument> pathArguments = path.getPathArguments();
129         if (pathArguments.get(0).getNodeType().equals(rootNode.getNodeType())) {
130             final List<PathArgument> relPath = pathArguments.subList(1, pathArguments.size());
131             final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(rootNode, relPath);
132             if (possibleNode.isPresent()) {
133                 return possibleNode.get();
134             }
135         }
136
137         return null;
138     }
139
140     private static NormalizedNode<?, ?> getNodeReferencedByLeafref(final RevisionAwareXPath xPath,
141             final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext,
142             final TypedSchemaNode correspondingSchemaNode, final Object nodeValue) {
143         final NormalizedNode<?, ?> referencedNode = xPath.isAbsolute() ? getNodeReferencedByAbsoluteLeafref(xPath,
144                 currentNodeContext, schemaContext, correspondingSchemaNode) : getNodeReferencedByRelativeLeafref(xPath,
145                 currentNodeContext, schemaContext, correspondingSchemaNode);
146
147         if (referencedNode instanceof LeafSetNode) {
148             return getReferencedLeafSetEntryNode((LeafSetNode<?>) referencedNode, nodeValue);
149         }
150
151         if (referencedNode instanceof LeafNode && referencedNode.getValue().equals(nodeValue)) {
152             return referencedNode;
153         }
154
155         return null;
156     }
157
158     private static NormalizedNode<?, ?> getNodeReferencedByAbsoluteLeafref(final RevisionAwareXPath xPath,
159             final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext,
160             final TypedSchemaNode correspondingSchemaNode) {
161         final LeafrefXPathStringParsingPathArgumentBuilder builder = new LeafrefXPathStringParsingPathArgumentBuilder(
162                 xPath.toString(), schemaContext, correspondingSchemaNode, currentNodeContext);
163         final List<PathArgument> pathArguments = builder.build();
164         final NormalizedNodeNavigator navigator = (NormalizedNodeNavigator) currentNodeContext.getNavigator();
165         final NormalizedNode<?, ?> rootNode = navigator.getRootNode();
166         if (pathArguments.get(0).getNodeType().equals(rootNode.getNodeType())) {
167             final List<PathArgument> relPath = pathArguments.subList(1, pathArguments.size());
168             final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(rootNode, relPath);
169             if (possibleNode.isPresent()) {
170                 return possibleNode.get();
171             }
172         }
173
174         return null;
175     }
176
177     private static NormalizedNode<?, ?> getNodeReferencedByRelativeLeafref(final RevisionAwareXPath xPath,
178             final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext,
179             final TypedSchemaNode correspondingSchemaNode) {
180         NormalizedNodeContext relativeNodeContext = currentNodeContext;
181         final StringBuilder xPathStringBuilder = new StringBuilder(xPath.toString());
182         // strip the relative path of all ../ at the beginning
183         while (xPathStringBuilder.indexOf("../") == 0) {
184             xPathStringBuilder.delete(0, 3);
185             relativeNodeContext = relativeNodeContext.getParent();
186         }
187
188         // add / to the beginning of the path so that it can be processed the same way as an absolute path
189         xPathStringBuilder.insert(0, '/');
190         final LeafrefXPathStringParsingPathArgumentBuilder builder = new LeafrefXPathStringParsingPathArgumentBuilder(
191                 xPathStringBuilder.toString(), schemaContext, correspondingSchemaNode, currentNodeContext);
192         final List<PathArgument> pathArguments = builder.build();
193         final NormalizedNode<?, ?> relativeNode = relativeNodeContext.getNode();
194         final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(relativeNode, pathArguments);
195         if (possibleNode.isPresent()) {
196             return possibleNode.get();
197         }
198
199         return null;
200     }
201
202     private static LeafSetEntryNode<?> getReferencedLeafSetEntryNode(final LeafSetNode<?> referencedNode,
203             final Object currentNodeValue) {
204         for (final LeafSetEntryNode<?> entryNode : referencedNode.getValue()) {
205             if (currentNodeValue.equals(entryNode.getValue())) {
206                 return entryNode;
207             }
208         }
209
210         return null;
211     }
212
213     // derived-from(node-set nodes, string identity) function as per https://tools.ietf.org/html/rfc7950#section-10.4.1
214     private static final Function DERIVED_FROM_FUNCTION = (context, args) -> {
215         if (args == null || args.size() != 1) {
216             throw new FunctionCallException("derived-from() takes two arguments: node-set nodes, string identity.");
217         }
218
219         if (!(args.get(0) instanceof String)) {
220             throw new FunctionCallException("Argument 'identity' of derived-from() function should be a String.");
221         }
222
223         final String identityArg = (String) args.get(0);
224
225         Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
226
227         final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
228         final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
229         final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext, currentNodeContext);
230
231         if (!(correspondingSchemaNode.getType() instanceof IdentityrefTypeDefinition)) {
232             return Boolean.FALSE;
233         }
234
235         if (!(currentNodeContext.getNode().getValue() instanceof QName)) {
236             return Boolean.FALSE;
237         }
238
239         final QName currentNodeValue = (QName) currentNodeContext.getNode().getValue();
240
241         final IdentitySchemaNode identityArgSchemaNode = getIdentitySchemaNodeFromString(identityArg, schemaContext,
242                 correspondingSchemaNode);
243         final IdentitySchemaNode currentNodeIdentitySchemaNode = getIdentitySchemaNodeFromQName(currentNodeValue,
244                 schemaContext);
245
246         final Set<IdentitySchemaNode> ancestorIdentities = new HashSet<>();
247         collectAncestorIdentities(currentNodeIdentitySchemaNode, ancestorIdentities);
248
249         return Boolean.valueOf(ancestorIdentities.contains(identityArgSchemaNode));
250     };
251
252     // derived-from-or-self(node-set nodes, string identity) function as per https://tools.ietf.org/html/rfc7950#section-10.4.2
253     private static final Function DERIVED_FROM_OR_SELF_FUNCTION = (context, args) -> {
254         if (args == null || args.size() != 1) {
255             throw new FunctionCallException("derived-from-or-self() takes two arguments: node-set nodes, string identity");
256         }
257
258         if (!(args.get(0) instanceof String)) {
259             throw new FunctionCallException("Argument 'identity' of derived-from-or-self() function should be a String.");
260         }
261
262         final String identityArg = (String) args.get(0);
263
264         Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
265
266         final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
267         final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
268         final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext, currentNodeContext);
269
270         if (!(correspondingSchemaNode.getType() instanceof IdentityrefTypeDefinition)) {
271             return Boolean.FALSE;
272         }
273
274         if (!(currentNodeContext.getNode().getValue() instanceof QName)) {
275             return Boolean.FALSE;
276         }
277
278         final QName currentNodeValue = (QName) currentNodeContext.getNode().getValue();
279
280         final IdentitySchemaNode identityArgSchemaNode = getIdentitySchemaNodeFromString(identityArg, schemaContext,
281                 correspondingSchemaNode);
282         final IdentitySchemaNode currentNodeIdentitySchemaNode = getIdentitySchemaNodeFromQName(currentNodeValue,
283                 schemaContext);
284         if (currentNodeIdentitySchemaNode.equals(identityArgSchemaNode)) {
285             return Boolean.TRUE;
286         }
287
288         final Set<IdentitySchemaNode> ancestorIdentities = new HashSet<>();
289         collectAncestorIdentities(currentNodeIdentitySchemaNode, ancestorIdentities);
290
291         return Boolean.valueOf(ancestorIdentities.contains(identityArgSchemaNode));
292     };
293
294     private static void collectAncestorIdentities(final IdentitySchemaNode identity,
295             final Set<IdentitySchemaNode> ancestorIdentities) {
296         for (final IdentitySchemaNode id : identity.getBaseIdentities()) {
297             collectAncestorIdentities(id, ancestorIdentities);
298             ancestorIdentities.add(id);
299         }
300     }
301
302     private static IdentitySchemaNode getIdentitySchemaNodeFromQName(final QName identityQName,
303             final SchemaContext schemaContext) {
304         final Module module = schemaContext.findModuleByNamespaceAndRevision(identityQName.getNamespace(),
305                 identityQName.getRevision());
306         return findIdentitySchemaNodeInModule(module, identityQName);
307     }
308
309     private static IdentitySchemaNode getIdentitySchemaNodeFromString(final String identity,
310             final SchemaContext schemaContext, final TypedSchemaNode correspondingSchemaNode) {
311         final List<String> identityPrefixAndName = COLON_SPLITTER.splitToList(identity);
312         final Module module = schemaContext.findModuleByNamespaceAndRevision(
313                 correspondingSchemaNode.getQName().getNamespace(), correspondingSchemaNode.getQName().getRevision());
314         if (identityPrefixAndName.size() == 2) {
315             // prefix of local module
316             if (identityPrefixAndName.get(0).equals(module.getPrefix())) {
317                 return findIdentitySchemaNodeInModule(module, QName.create(module.getQNameModule(),
318                         identityPrefixAndName.get(1)));
319             }
320
321             // prefix of imported module
322             for (final ModuleImport moduleImport : module.getImports()) {
323                 if (identityPrefixAndName.get(0).equals(moduleImport.getPrefix())) {
324                     final Module importedModule = schemaContext.findModuleByName(moduleImport.getModuleName(),
325                         moduleImport.getRevision());
326                     return findIdentitySchemaNodeInModule(importedModule, QName.create(
327                         importedModule.getQNameModule(), identityPrefixAndName.get(1)));
328                 }
329             }
330
331             throw new IllegalArgumentException(String.format("Cannot resolve prefix '%s' from identity '%s'.",
332                     identityPrefixAndName.get(0), identity));
333         }
334
335         if (identityPrefixAndName.size() == 1) { // without prefix
336             return findIdentitySchemaNodeInModule(module, QName.create(module.getQNameModule(),
337                     identityPrefixAndName.get(0)));
338         }
339
340         throw new IllegalArgumentException(String.format("Malformed identity argument: %s.", identity));
341     }
342
343     private static IdentitySchemaNode findIdentitySchemaNodeInModule(final Module module, final QName identityQName) {
344         for (final IdentitySchemaNode id : module.getIdentities()) {
345             if (identityQName.equals(id.getQName())) {
346                 return id;
347             }
348         }
349
350         throw new IllegalArgumentException(String.format("Identity %s does not have a corresponding" +
351                 " identity schema node in the module %s.", identityQName, module));
352     }
353
354     // enum-value(node-set nodes) function as per https://tools.ietf.org/html/rfc7950#section-10.5.1
355     private static final Function ENUM_VALUE_FUNCTION = (context, args) -> {
356         if (!args.isEmpty()) {
357             throw new FunctionCallException("enum-value() takes one argument: node-set nodes.");
358         }
359
360         Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
361
362         final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
363         final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
364         final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext,
365             currentNodeContext);
366
367         final TypeDefinition<?> nodeType = correspondingSchemaNode.getType();
368         if (!(nodeType instanceof EnumTypeDefinition)) {
369             return DOUBLE_NAN;
370         }
371
372         final Object nodeValue = currentNodeContext.getNode().getValue();
373         if (!(nodeValue instanceof String)) {
374             return DOUBLE_NAN;
375         }
376
377         final EnumTypeDefinition enumerationType = (EnumTypeDefinition) nodeType;
378         final String enumName = (String) nodeValue;
379
380         return getEnumValue(enumerationType, enumName);
381     };
382
383     private static int getEnumValue(final EnumTypeDefinition enumerationType, final String enumName) {
384         for (final EnumTypeDefinition.EnumPair enumPair : enumerationType.getValues()) {
385             if (enumName.equals(enumPair.getName())) {
386                 return enumPair.getValue();
387             }
388         }
389
390         throw new IllegalStateException(String.format("Enum %s does not belong to enumeration %s.",
391                 enumName, enumerationType));
392     }
393
394     // bit-is-set(node-set nodes, string bit-name) function as per https://tools.ietf.org/html/rfc7950#section-10.6.1
395     private static final Function BIT_IS_SET_FUNCTION = (context, args) -> {
396         if (args == null || args.size() != 1) {
397             throw new FunctionCallException("bit-is-set() takes two arguments: node-set nodes, string bit-name");
398         }
399
400         if (!(args.get(0) instanceof String)) {
401             throw new FunctionCallException("Argument bit-name of bit-is-set() function should be a String");
402         }
403
404         final String bitName = (String) args.get(0);
405
406         Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
407
408         final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
409         final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
410         final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext, currentNodeContext);
411
412         final TypeDefinition<?> nodeType = correspondingSchemaNode.getType();
413         if (!(nodeType instanceof BitsTypeDefinition)) {
414             return Boolean.FALSE;
415         }
416
417         final Object nodeValue = currentNodeContext.getNode().getValue();
418         if (!(nodeValue instanceof Set)) {
419             return Boolean.FALSE;
420         }
421
422         final BitsTypeDefinition bitsType = (BitsTypeDefinition) nodeType;
423         Preconditions.checkState(containsBit(bitsType, bitName), "Bit %s does not belong to bits %s.", bitName,
424             bitsType);
425         return Boolean.valueOf(((Set<?>)nodeValue).contains(bitName));
426     };
427
428     private static boolean containsBit(final BitsTypeDefinition bitsType, final String bitName) {
429         for (BitsTypeDefinition.Bit bit : bitsType.getBits()) {
430             if (bitName.equals(bit.getName())) {
431                 return true;
432             }
433         }
434
435         return false;
436     }
437
438     private static SchemaContext getSchemaContext(final NormalizedNodeContext normalizedNodeContext) {
439         final ContextSupport contextSupport = normalizedNodeContext.getContextSupport();
440         Verify.verify(contextSupport instanceof NormalizedNodeContextSupport, "Unhandled context support %s",
441                 contextSupport.getClass());
442         return ((NormalizedNodeContextSupport) contextSupport).getSchemaContext();
443     }
444
445     private static TypedSchemaNode getCorrespondingTypedSchemaNode(final SchemaContext schemaContext,
446             final NormalizedNodeContext currentNodeContext) {
447         Iterator<NormalizedNodeContext> ancestorOrSelfAxisIterator;
448         try {
449             ancestorOrSelfAxisIterator = currentNodeContext.getContextSupport().getNavigator()
450                     .getAncestorOrSelfAxisIterator(currentNodeContext);
451         } catch (UnsupportedAxisException ex) {
452             throw new JaxenRuntimeException(ex);
453         }
454
455         final Deque<QName> schemaPathToCurrentNode = new ArrayDeque<>();
456         while (ancestorOrSelfAxisIterator.hasNext()) {
457             final NormalizedNode<?, ?> nextNode = ancestorOrSelfAxisIterator.next().getNode();
458             if (!(nextNode instanceof MapNode) && !(nextNode instanceof LeafSetNode)
459                     && !(nextNode instanceof AugmentationNode)) {
460                 schemaPathToCurrentNode.addFirst(nextNode.getNodeType());
461             }
462         }
463
464         final SchemaNode schemaNode = SchemaContextUtil.findNodeInSchemaContext(schemaContext, schemaPathToCurrentNode);
465
466         Preconditions.checkNotNull(schemaNode, "Node %s does not have a corresponding SchemaNode in the SchemaContext.",
467                 currentNodeContext.getNode());
468         Preconditions.checkState(schemaNode instanceof TypedSchemaNode, "Node %s must be a leaf or a leaf-list.",
469                 currentNodeContext.getNode());
470         return (TypedSchemaNode) schemaNode;
471     }
472
473     // Singleton instance of reuse
474     private static final YangFunctionContext INSTANCE = new YangFunctionContext();
475
476     private YangFunctionContext() {
477     }
478
479     static YangFunctionContext getInstance() {
480         return INSTANCE;
481     }
482
483     @Override
484     public Function getFunction(final String namespaceURI, final String prefix, final String localName)
485             throws UnresolvableException {
486         if (prefix == null) {
487             switch (localName) {
488                 case "bit-is-set":
489                     return BIT_IS_SET_FUNCTION;
490                 case "current":
491                     return CURRENT_FUNCTION;
492                 case "deref":
493                     return DEREF_FUNCTION;
494                 case "derived-from":
495                     return DERIVED_FROM_FUNCTION;
496                 case "derived-from-or-self":
497                     return DERIVED_FROM_OR_SELF_FUNCTION;
498                 case "enum-value":
499                     return ENUM_VALUE_FUNCTION;
500                 case "re-match":
501                     return REMATCH_FUNCTION;
502             }
503         }
504
505         return XPATH_FUNCTION_CONTEXT.getFunction(namespaceURI, prefix, localName);
506     }
507 }