YANG XPath functions - unit tests and bugfix
[yangtools.git] / yang / yang-data-jaxen / src / main / java / org / opendaylight / yangtools / yang / data / jaxen / YangFunctionContext.java
index c2dae8995932b2220c4fdac69e68b1064775c40a..429697d2f73ebbb718f06979e17404ffebb4b2cf 100644 (file)
  */
 package org.opendaylight.yangtools.yang.data.jaxen;
 
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
 import com.google.common.base.Verify;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
-import org.jaxen.Context;
+import java.util.Set;
+import org.jaxen.ContextSupport;
 import org.jaxen.Function;
 import org.jaxen.FunctionCallException;
 import org.jaxen.FunctionContext;
+import org.jaxen.JaxenRuntimeException;
 import org.jaxen.UnresolvableException;
+import org.jaxen.UnsupportedAxisException;
 import org.jaxen.XPathFunctionContext;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
+import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.ModuleImport;
+import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.TypedSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
+import org.opendaylight.yangtools.yang.model.util.RegexUtils;
+import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
 
 /**
- * A {@link FunctionContext} which contains also to YANG-specific current() function.
+ * A {@link FunctionContext} which contains also YANG-specific functions current(), re-match(), deref(),
+ * derived-from(), derived-from-or-self(), enum-value() and bit-is-set().
  */
 final class YangFunctionContext implements FunctionContext {
+    private static final Splitter COLON_SPLITTER = Splitter.on(':');
+    private static final Double DOUBLE_NAN = Double.NaN;
+
     // Core XPath functions, as per http://tools.ietf.org/html/rfc6020#section-6.4.1
     private static final FunctionContext XPATH_FUNCTION_CONTEXT = new XPathFunctionContext(false);
     // current() function, as per http://tools.ietf.org/html/rfc6020#section-6.4.1
-    private static final Function CURRENT_FUNCTION = new Function() {
-        @Override
-        public NormalizedNodeContext call(final Context context, @SuppressWarnings("rawtypes") final List args)
-                throws FunctionCallException {
-            if (!args.isEmpty()) {
-                throw new FunctionCallException("current() takes no arguments.");
+    private static final Function CURRENT_FUNCTION = (context, args) -> {
+        if (!args.isEmpty()) {
+            throw new FunctionCallException("current() takes no arguments.");
+        }
+
+        Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
+        return ((NormalizedNodeContext) context);
+    };
+
+    // re-match(string subject, string pattern) function as per https://tools.ietf.org/html/rfc7950#section-10.2.1
+    private static final Function REMATCH_FUNCTION = (context, args) -> {
+        if (args == null || args.size() != 2) {
+            throw new FunctionCallException("re-match() takes two arguments: string subject, string pattern.");
+        }
+
+        if (!(args.get(0) instanceof String)) {
+            throw new FunctionCallException("First argument of re-match() should be a String.");
+        }
+
+        if (!(args.get(1) instanceof String)) {
+            throw new FunctionCallException("Second argument of re-match() should be a String.");
+        }
+
+        final String subject = (String) args.get(0);
+        final String rawPattern = (String) args.get(1);
+
+        final String pattern = RegexUtils.getJavaRegexFromXSD(rawPattern);
+
+        return (Boolean) subject.matches(pattern);
+    };
+
+    // deref(node-set nodes) function as per https://tools.ietf.org/html/rfc7950#section-10.3.1
+    private static final Function DEREF_FUNCTION = (context, args) -> {
+        if (!args.isEmpty()) {
+            throw new FunctionCallException("deref() takes only one argument: node-set nodes.");
+        }
+
+        Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
+
+        final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
+        final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
+        final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext, currentNodeContext);
+
+        final Object nodeValue = currentNodeContext.getNode().getValue();
+
+        if (correspondingSchemaNode.getType() instanceof InstanceIdentifierTypeDefinition
+                && nodeValue instanceof YangInstanceIdentifier) {
+            return getNodeReferencedByInstanceIdentifier((YangInstanceIdentifier) nodeValue, currentNodeContext);
+        }
+
+        if (correspondingSchemaNode.getType() instanceof LeafrefTypeDefinition) {
+            final LeafrefTypeDefinition leafrefType = (LeafrefTypeDefinition) correspondingSchemaNode.getType();
+            final RevisionAwareXPath xPath = leafrefType.getPathStatement();
+            return getNodeReferencedByLeafref(xPath, currentNodeContext, schemaContext, correspondingSchemaNode, nodeValue);
+        }
+
+        return null;
+    };
+
+    private static NormalizedNode<?, ?> getNodeReferencedByInstanceIdentifier(final YangInstanceIdentifier path,
+            final NormalizedNodeContext currentNodeContext) {
+        final NormalizedNodeNavigator navigator = (NormalizedNodeNavigator) currentNodeContext.getNavigator();
+        final NormalizedNode<?, ?> rootNode = navigator.getRootNode();
+        final List<PathArgument> pathArguments = path.getPathArguments();
+        if (pathArguments.get(0).getNodeType().equals(rootNode.getNodeType())) {
+            final List<PathArgument> relPath = pathArguments.subList(1, pathArguments.size());
+            final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(rootNode, relPath);
+            if (possibleNode.isPresent()) {
+                return possibleNode.get();
+            }
+        }
+
+        return null;
+    }
+
+    private static NormalizedNode<?, ?> getNodeReferencedByLeafref(final RevisionAwareXPath xPath,
+            final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext,
+            final TypedSchemaNode correspondingSchemaNode, final Object nodeValue) {
+        final NormalizedNode<?, ?> referencedNode = xPath.isAbsolute() ? getNodeReferencedByAbsoluteLeafref(xPath,
+                currentNodeContext, schemaContext, correspondingSchemaNode) : getNodeReferencedByRelativeLeafref(xPath,
+                currentNodeContext, schemaContext, correspondingSchemaNode);
+
+        if (referencedNode instanceof LeafSetNode) {
+            return getReferencedLeafSetEntryNode((LeafSetNode<?>) referencedNode, nodeValue);
+        }
+
+        if (referencedNode instanceof LeafNode && referencedNode.getValue().equals(nodeValue)) {
+            return referencedNode;
+        }
+
+        return null;
+    }
+
+    private static NormalizedNode<?, ?> getNodeReferencedByAbsoluteLeafref(final RevisionAwareXPath xPath,
+            final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext,
+            final TypedSchemaNode correspondingSchemaNode) {
+        final LeafrefXPathStringParsingPathArgumentBuilder builder = new LeafrefXPathStringParsingPathArgumentBuilder(
+                xPath.toString(), schemaContext, correspondingSchemaNode, currentNodeContext);
+        final List<PathArgument> pathArguments = builder.build();
+        final NormalizedNodeNavigator navigator = (NormalizedNodeNavigator) currentNodeContext.getNavigator();
+        final NormalizedNode<?, ?> rootNode = navigator.getRootNode();
+        if (pathArguments.get(0).getNodeType().equals(rootNode.getNodeType())) {
+            final List<PathArgument> relPath = pathArguments.subList(1, pathArguments.size());
+            final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(rootNode, relPath);
+            if (possibleNode.isPresent()) {
+                return possibleNode.get();
+            }
+        }
+
+        return null;
+    }
+
+    private static NormalizedNode<?, ?> getNodeReferencedByRelativeLeafref(final RevisionAwareXPath xPath,
+            final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext,
+            final TypedSchemaNode correspondingSchemaNode) {
+        NormalizedNodeContext relativeNodeContext = currentNodeContext;
+        final StringBuilder xPathStringBuilder = new StringBuilder(xPath.toString());
+        // strip the relative path of all ../ at the beginning
+        while (xPathStringBuilder.indexOf("../") == 0) {
+            xPathStringBuilder.delete(0, 3);
+            relativeNodeContext = relativeNodeContext.getParent();
+        }
+
+        // add / to the beginning of the path so that it can be processed the same way as an absolute path
+        xPathStringBuilder.insert(0, '/');
+        final LeafrefXPathStringParsingPathArgumentBuilder builder = new LeafrefXPathStringParsingPathArgumentBuilder(
+                xPathStringBuilder.toString(), schemaContext, correspondingSchemaNode, currentNodeContext);
+        final List<PathArgument> pathArguments = builder.build();
+        final NormalizedNode<?, ?> relativeNode = relativeNodeContext.getNode();
+        final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(relativeNode, pathArguments);
+        if (possibleNode.isPresent()) {
+            return possibleNode.get();
+        }
+
+        return null;
+    }
+
+    private static LeafSetEntryNode<?> getReferencedLeafSetEntryNode(final LeafSetNode<?> referencedNode,
+            final Object currentNodeValue) {
+        for (final LeafSetEntryNode<?> entryNode : referencedNode.getValue()) {
+            if (currentNodeValue.equals(entryNode.getValue())) {
+                return entryNode;
+            }
+        }
+
+        return null;
+    }
+
+    // derived-from(node-set nodes, string identity) function as per https://tools.ietf.org/html/rfc7950#section-10.4.1
+    private static final Function DERIVED_FROM_FUNCTION = (context, args) -> {
+        if (args == null || args.size() != 1) {
+            throw new FunctionCallException("derived-from() takes two arguments: node-set nodes, string identity.");
+        }
+
+        if (!(args.get(0) instanceof String)) {
+            throw new FunctionCallException("Argument 'identity' of derived-from() function should be a String.");
+        }
+
+        final String identityArg = (String) args.get(0);
+
+        Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
+
+        final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
+        final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
+        final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext, currentNodeContext);
+
+        if (!(correspondingSchemaNode.getType() instanceof IdentityrefTypeDefinition)) {
+            return Boolean.FALSE;
+        }
+
+        if (!(currentNodeContext.getNode().getValue() instanceof QName)) {
+            return Boolean.FALSE;
+        }
+
+        final QName currentNodeValue = (QName) currentNodeContext.getNode().getValue();
+
+        final IdentitySchemaNode identityArgSchemaNode = getIdentitySchemaNodeFromString(identityArg, schemaContext,
+                correspondingSchemaNode);
+        final IdentitySchemaNode currentNodeIdentitySchemaNode = getIdentitySchemaNodeFromQName(currentNodeValue,
+                schemaContext);
+
+        final Set<IdentitySchemaNode> ancestorIdentities = new HashSet<>();
+        collectAncestorIdentities(currentNodeIdentitySchemaNode, ancestorIdentities);
+
+        return Boolean.valueOf(ancestorIdentities.contains(identityArgSchemaNode));
+    };
+
+    // derived-from-or-self(node-set nodes, string identity) function as per https://tools.ietf.org/html/rfc7950#section-10.4.2
+    private static final Function DERIVED_FROM_OR_SELF_FUNCTION = (context, args) -> {
+        if (args == null || args.size() != 1) {
+            throw new FunctionCallException("derived-from-or-self() takes two arguments: node-set nodes, string identity");
+        }
+
+        if (!(args.get(0) instanceof String)) {
+            throw new FunctionCallException("Argument 'identity' of derived-from-or-self() function should be a String.");
+        }
+
+        final String identityArg = (String) args.get(0);
+
+        Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
+
+        final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
+        final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
+        final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext, currentNodeContext);
+
+        if (!(correspondingSchemaNode.getType() instanceof IdentityrefTypeDefinition)) {
+            return Boolean.FALSE;
+        }
+
+        if (!(currentNodeContext.getNode().getValue() instanceof QName)) {
+            return Boolean.FALSE;
+        }
+
+        final QName currentNodeValue = (QName) currentNodeContext.getNode().getValue();
+
+        final IdentitySchemaNode identityArgSchemaNode = getIdentitySchemaNodeFromString(identityArg, schemaContext,
+                correspondingSchemaNode);
+        final IdentitySchemaNode currentNodeIdentitySchemaNode = getIdentitySchemaNodeFromQName(currentNodeValue,
+                schemaContext);
+        if (currentNodeIdentitySchemaNode.equals(identityArgSchemaNode)) {
+            return Boolean.TRUE;
+        }
+
+        final Set<IdentitySchemaNode> ancestorIdentities = new HashSet<>();
+        collectAncestorIdentities(currentNodeIdentitySchemaNode, ancestorIdentities);
+
+        return Boolean.valueOf(ancestorIdentities.contains(identityArgSchemaNode));
+    };
+
+    private static void collectAncestorIdentities(final IdentitySchemaNode identity,
+            final Set<IdentitySchemaNode> ancestorIdentities) {
+        for (final IdentitySchemaNode id : identity.getBaseIdentities()) {
+            collectAncestorIdentities(id, ancestorIdentities);
+            ancestorIdentities.add(id);
+        }
+    }
+
+    private static IdentitySchemaNode getIdentitySchemaNodeFromQName(final QName identityQName,
+            final SchemaContext schemaContext) {
+        final Module module = schemaContext.findModuleByNamespaceAndRevision(identityQName.getNamespace(),
+                identityQName.getRevision());
+        return findIdentitySchemaNodeInModule(module, identityQName);
+    }
+
+    private static IdentitySchemaNode getIdentitySchemaNodeFromString(final String identity,
+            final SchemaContext schemaContext, final TypedSchemaNode correspondingSchemaNode) {
+        final List<String> identityPrefixAndName = COLON_SPLITTER.splitToList(identity);
+        final Module module = schemaContext.findModuleByNamespaceAndRevision(
+                correspondingSchemaNode.getQName().getNamespace(), correspondingSchemaNode.getQName().getRevision());
+        if (identityPrefixAndName.size() == 2) {
+            // prefix of local module
+            if (identityPrefixAndName.get(0).equals(module.getPrefix())) {
+                return findIdentitySchemaNodeInModule(module, QName.create(module.getQNameModule(),
+                        identityPrefixAndName.get(1)));
+            }
+
+            // prefix of imported module
+            for (final ModuleImport moduleImport : module.getImports()) {
+                if (identityPrefixAndName.get(0).equals(moduleImport.getPrefix())) {
+                    final Module importedModule = schemaContext.findModuleByName(moduleImport.getModuleName(),
+                        moduleImport.getRevision());
+                    return findIdentitySchemaNodeInModule(importedModule, QName.create(
+                        importedModule.getQNameModule(), identityPrefixAndName.get(1)));
+                }
+            }
+
+            throw new IllegalArgumentException(String.format("Cannot resolve prefix '%s' from identity '%s'.",
+                    identityPrefixAndName.get(0), identity));
+        }
+
+        if (identityPrefixAndName.size() == 1) { // without prefix
+            return findIdentitySchemaNodeInModule(module, QName.create(module.getQNameModule(),
+                    identityPrefixAndName.get(0)));
+        }
+
+        throw new IllegalArgumentException(String.format("Malformed identity argument: %s.", identity));
+    }
+
+    private static IdentitySchemaNode findIdentitySchemaNodeInModule(final Module module, final QName identityQName) {
+        for (final IdentitySchemaNode id : module.getIdentities()) {
+            if (identityQName.equals(id.getQName())) {
+                return id;
+            }
+        }
+
+        throw new IllegalArgumentException(String.format("Identity %s does not have a corresponding" +
+                " identity schema node in the module %s.", identityQName, module));
+    }
+
+    // enum-value(node-set nodes) function as per https://tools.ietf.org/html/rfc7950#section-10.5.1
+    private static final Function ENUM_VALUE_FUNCTION = (context, args) -> {
+        if (!args.isEmpty()) {
+            throw new FunctionCallException("enum-value() takes one argument: node-set nodes.");
+        }
+
+        Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
+
+        final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
+        final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
+        final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext,
+            currentNodeContext);
+
+        final TypeDefinition<?> nodeType = correspondingSchemaNode.getType();
+        if (!(nodeType instanceof EnumTypeDefinition)) {
+            return DOUBLE_NAN;
+        }
+
+        final Object nodeValue = currentNodeContext.getNode().getValue();
+        if (!(nodeValue instanceof String)) {
+            return DOUBLE_NAN;
+        }
+
+        final EnumTypeDefinition enumerationType = (EnumTypeDefinition) nodeType;
+        final String enumName = (String) nodeValue;
+
+        return getEnumValue(enumerationType, enumName);
+    };
+
+    private static int getEnumValue(final EnumTypeDefinition enumerationType, final String enumName) {
+        for (final EnumTypeDefinition.EnumPair enumPair : enumerationType.getValues()) {
+            if (enumName.equals(enumPair.getName())) {
+                return enumPair.getValue();
             }
+        }
+
+        throw new IllegalStateException(String.format("Enum %s does not belong to enumeration %s.",
+                enumName, enumerationType));
+    }
+
+    // bit-is-set(node-set nodes, string bit-name) function as per https://tools.ietf.org/html/rfc7950#section-10.6.1
+    private static final Function BIT_IS_SET_FUNCTION = (context, args) -> {
+        if (args == null || args.size() != 1) {
+            throw new FunctionCallException("bit-is-set() takes two arguments: node-set nodes, string bit-name");
+        }
+
+        if (!(args.get(0) instanceof String)) {
+            throw new FunctionCallException("Argument bit-name of bit-is-set() function should be a String");
+        }
+
+        final String bitName = (String) args.get(0);
+
+        Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
+
+        final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
+        final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
+        final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext, currentNodeContext);
+
+        final TypeDefinition<?> nodeType = correspondingSchemaNode.getType();
+        if (!(nodeType instanceof BitsTypeDefinition)) {
+            return Boolean.FALSE;
+        }
 
-            Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
-            return ((NormalizedNodeContext) context);
+        final Object nodeValue = currentNodeContext.getNode().getValue();
+        if (!(nodeValue instanceof Set)) {
+            return Boolean.FALSE;
         }
+
+        final BitsTypeDefinition bitsType = (BitsTypeDefinition) nodeType;
+        Preconditions.checkState(containsBit(bitsType, bitName), "Bit %s does not belong to bits %s.", bitName,
+            bitsType);
+        return Boolean.valueOf(((Set<?>)nodeValue).contains(bitName));
     };
 
+    private static boolean containsBit(final BitsTypeDefinition bitsType, final String bitName) {
+        for (BitsTypeDefinition.Bit bit : bitsType.getBits()) {
+            if (bitName.equals(bit.getName())) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private static SchemaContext getSchemaContext(final NormalizedNodeContext normalizedNodeContext) {
+        final ContextSupport contextSupport = normalizedNodeContext.getContextSupport();
+        Verify.verify(contextSupport instanceof NormalizedNodeContextSupport, "Unhandled context support %s",
+                contextSupport.getClass());
+        return ((NormalizedNodeContextSupport) contextSupport).getSchemaContext();
+    }
+
+    private static TypedSchemaNode getCorrespondingTypedSchemaNode(final SchemaContext schemaContext,
+            final NormalizedNodeContext currentNodeContext) {
+        Iterator<NormalizedNodeContext> ancestorOrSelfAxisIterator;
+        try {
+            ancestorOrSelfAxisIterator = currentNodeContext.getContextSupport().getNavigator()
+                    .getAncestorOrSelfAxisIterator(currentNodeContext);
+        } catch (UnsupportedAxisException ex) {
+            throw new JaxenRuntimeException(ex);
+        }
+
+        final Deque<QName> schemaPathToCurrentNode = new ArrayDeque<>();
+        while (ancestorOrSelfAxisIterator.hasNext()) {
+            final NormalizedNode<?, ?> nextNode = ancestorOrSelfAxisIterator.next().getNode();
+            if (!(nextNode instanceof MapNode) && !(nextNode instanceof LeafSetNode)
+                    && !(nextNode instanceof AugmentationNode)) {
+                schemaPathToCurrentNode.addFirst(nextNode.getNodeType());
+            }
+        }
+
+        final SchemaNode schemaNode = SchemaContextUtil.findNodeInSchemaContext(schemaContext, schemaPathToCurrentNode);
+
+        Preconditions.checkNotNull(schemaNode, "Node %s does not have a corresponding SchemaNode in the SchemaContext.",
+                currentNodeContext.getNode());
+        Preconditions.checkState(schemaNode instanceof TypedSchemaNode, "Node %s must be a leaf or a leaf-list.",
+                currentNodeContext.getNode());
+        return (TypedSchemaNode) schemaNode;
+    }
+
     // Singleton instance of reuse
     private static final YangFunctionContext INSTANCE = new YangFunctionContext();
 
@@ -47,10 +481,27 @@ final class YangFunctionContext implements FunctionContext {
     }
 
     @Override
-    public Function getFunction(final String namespaceURI, final String prefix, final String localName) throws UnresolvableException {
-        if (prefix == null && "current".equals(localName)) {
-            return CURRENT_FUNCTION;
+    public Function getFunction(final String namespaceURI, final String prefix, final String localName)
+            throws UnresolvableException {
+        if (prefix == null) {
+            switch (localName) {
+                case "bit-is-set":
+                    return BIT_IS_SET_FUNCTION;
+                case "current":
+                    return CURRENT_FUNCTION;
+                case "deref":
+                    return DEREF_FUNCTION;
+                case "derived-from":
+                    return DERIVED_FROM_FUNCTION;
+                case "derived-from-or-self":
+                    return DERIVED_FROM_OR_SELF_FUNCTION;
+                case "enum-value":
+                    return ENUM_VALUE_FUNCTION;
+                case "re-match":
+                    return REMATCH_FUNCTION;
+            }
         }
+
         return XPATH_FUNCTION_CONTEXT.getFunction(namespaceURI, prefix, localName);
     }
 }