Bug 7847: Implement YANG 1.1 XPath functions in YangFunctionContext 25/54525/3
authorIgor Foltin <ifoltin@cisco.com>
Fri, 7 Apr 2017 15:17:23 +0000 (17:17 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Mon, 10 Apr 2017 12:39:36 +0000 (14:39 +0200)
Introduce implementation of functions re-match, deref, derived-from,
derived-from-or-self, enum-value and bit-is-set in the yang-data-jaxen's
YangFunctionContext.

The functionality introduced in this patch is beta-quality and will
need more thorough testing in the future

Change-Id: Ia89f8b50dd7537cfafc2297ada8720648050bdbd
Signed-off-by: Igor Foltin <ifoltin@cisco.com>
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
18 files changed:
yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/JaxenDocument.java [moved from yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/JaxenDocumentContext.java with 79% similarity]
yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/JaxenSchemaContext.java
yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/LeafrefXPathStringParsingPathArgumentBuilder.java [new file with mode: 0644]
yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/NormalizedNodeContextSupport.java
yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/NormalizedNodeNavigator.java
yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/YangFunctionContext.java
yang/yang-data-jaxen/src/test/java/org/opendaylight/yangtools/yang/data/jaxen/YangXPathFunctionsTest.java [new file with mode: 0644]
yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/bit-is-set-function/foo.yang [new file with mode: 0644]
yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/deref-function-iid/foo.yang [new file with mode: 0644]
yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/deref-function-leafref/foo.yang [new file with mode: 0644]
yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/derived-from-function/bar.yang [new file with mode: 0644]
yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/derived-from-function/foo.yang [new file with mode: 0644]
yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/enum-value-function/foo.yang [new file with mode: 0644]
yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/RegexUtils.java [new file with mode: 0644]
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/PatternStatementImpl.java
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/Utils.java
yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/Bug5410Test.java
yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/stmt/Bug4079Test.java

similarity index 79%
rename from yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/JaxenDocumentContext.java
rename to yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/JaxenDocument.java
index 0b0e26ebb3a2c07c2f12b7ec5d3b58abc3461362..a929061c34f0a0720f38f2d3d8ed00514d0a4a56 100644 (file)
@@ -11,12 +11,15 @@ import com.google.common.base.Preconditions;
 import javax.annotation.Nonnull;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.xpath.XPathDocument;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
 final class JaxenDocument implements XPathDocument {
     private final NormalizedNode<?, ?> root;
+    private final SchemaContext context;
 
     JaxenDocument(final JaxenSchemaContext context, final NormalizedNode<?, ?> root) {
         this.root = Preconditions.checkNotNull(root);
+        this.context = context.getSchemaContext();
     }
 
     @Nonnull
@@ -24,4 +27,9 @@ final class JaxenDocument implements XPathDocument {
     public NormalizedNode<?, ?> getRootNode() {
         return root;
     }
+
+    @Nonnull
+    SchemaContext getSchemaContext() {
+        return context;
+    }
 }
index 91db1a25b0d93ac13b6cc522e94c936ef9c3b841..b781381799d326e67ca5badc84fb512788613368 100644 (file)
@@ -45,4 +45,9 @@ final class JaxenSchemaContext implements XPathSchemaContext {
     public XPathDocument createDocument(@Nonnull final NormalizedNode<?, ?> documentRoot) {
         return new JaxenDocument(this, documentRoot);
     }
+
+    @Nonnull
+    SchemaContext getSchemaContext() {
+        return context;
+    }
 }
diff --git a/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/LeafrefXPathStringParsingPathArgumentBuilder.java b/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/LeafrefXPathStringParsingPathArgumentBuilder.java
new file mode 100644 (file)
index 0000000..1d83982
--- /dev/null
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) 2017 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.yangtools.yang.data.jaxen;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Optional;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.annotation.RegEx;
+import org.opendaylight.yangtools.concepts.Builder;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.ModuleImport;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.TypedSchemaNode;
+
+@Beta
+final class LeafrefXPathStringParsingPathArgumentBuilder implements Builder<List<PathArgument>> {
+
+    private static final String UP_ONE_LEVEL = "..";
+    private static final String CURRENT_FUNCTION_INVOCATION_STR = "current()";
+
+    @RegEx
+    private static final String NODE_IDENTIFIER_STR = "([A-Za-z_][A-Za-z0-9_\\.-]*:)?([A-Za-z_][A-Za-z0-9_\\.-]*)";
+
+    /**
+     * Pattern matching node-identifier YANG ABNF token
+     */
+    private static final Pattern NODE_IDENTIFIER_PATTERN = Pattern.compile(NODE_IDENTIFIER_STR);
+
+    /**
+     * Matcher matching WSP YANG ABNF token
+     *
+     */
+    private static final CharMatcher WSP = CharMatcher.anyOf(" \t");
+
+    /**
+     * Matcher matching IDENTIFIER first char token.
+     *
+     */
+    private static final CharMatcher IDENTIFIER_FIRST_CHAR = CharMatcher.inRange('a', 'z')
+            .or(CharMatcher.inRange('A', 'Z')).or(CharMatcher.is('_')).precomputed();
+    /**
+     *
+     * Matcher matching IDENTIFIER token
+     *
+     */
+    private static final CharMatcher IDENTIFIER = IDENTIFIER_FIRST_CHAR.or(CharMatcher.inRange('0', '9'))
+            .or(CharMatcher.anyOf(".-")).precomputed();
+
+    private static final Splitter SLASH_SPLITTER = Splitter.on('/');
+
+    private static final char SLASH = '/';
+    private static final char COLON = ':';
+    private static final char EQUALS = '=';
+    private static final char PRECONDITION_START = '[';
+    private static final char PRECONDITION_END = ']';
+
+    private final String xPathString;
+    private final SchemaContext schemaContext;
+    private final TypedSchemaNode schemaNode;
+    private final NormalizedNodeContext currentNodeCtx;
+    private final List<PathArgument> product = new ArrayList<>();
+
+    private int offset = 0;
+
+    LeafrefXPathStringParsingPathArgumentBuilder(final String xPathString, final SchemaContext schemaContext,
+            final TypedSchemaNode schemaNode, final NormalizedNodeContext currentNodeCtx) {
+        this.xPathString = xPathString;
+        this.schemaContext = schemaContext;
+        this.schemaNode = schemaNode;
+        this.currentNodeCtx = currentNodeCtx;
+    }
+
+    @Override
+    public List<PathArgument> build() {
+        while (!allCharactersConsumed()) {
+            product.add(computeNextArgument());
+        }
+        return ImmutableList.copyOf(product);
+    }
+
+    private PathArgument computeNextArgument() {
+        checkValid(SLASH == currentChar(), "Identifier must start with '/'.");
+        skipCurrentChar();
+        checkValid(!allCharactersConsumed(), "Identifier cannot end with '/'.");
+
+        final QName name = nextQName();
+        if (allCharactersConsumed() || SLASH == currentChar()) {
+            return new NodeIdentifier(name);
+        } else {
+            checkValid(PRECONDITION_START == currentChar(), "Last element must be identifier, predicate or '/'");
+            return computeIdentifierWithPredicate(name);
+        }
+    }
+
+    private PathArgument computeIdentifierWithPredicate(final QName name) {
+        product.add(new NodeIdentifier(name));
+
+        ImmutableMap.Builder<QName, Object> keyValues = ImmutableMap.builder();
+        while (!allCharactersConsumed() && PRECONDITION_START == currentChar()) {
+            skipCurrentChar();
+            skipWhitespaces();
+            final QName key = nextQName();
+
+            skipWhitespaces();
+            checkCurrentAndSkip(EQUALS, "Precondition must contain '='");
+            skipWhitespaces();
+            final Object keyValue = nextCurrentFunctionPathValue();
+            skipWhitespaces();
+            checkCurrentAndSkip(PRECONDITION_END, "Precondition must ends with ']'");
+
+            keyValues.put(key, keyValue);
+        }
+        return new NodeIdentifierWithPredicates(name, keyValues.build());
+    }
+
+    private Object nextCurrentFunctionPathValue() {
+        final String xPathSubStr = xPathString.substring(offset);
+        final String pathKeyExpression = xPathSubStr.substring(0, xPathSubStr.indexOf(PRECONDITION_END));
+        final String relPathKeyExpression = pathKeyExpression.substring(CURRENT_FUNCTION_INVOCATION_STR.length());
+
+        offset += CURRENT_FUNCTION_INVOCATION_STR.length();
+        skipWhitespaces();
+        checkCurrentAndSkip(SLASH, "Expression 'current()' must be followed by slash.");
+        skipWhitespaces();
+
+        final List<String> pathComponents = SLASH_SPLITTER.trimResults().omitEmptyStrings()
+                .splitToList(relPathKeyExpression);
+        checkValid(!pathComponents.isEmpty(), "Malformed path key expression: '%s'.", pathKeyExpression);
+
+        boolean inNodeIdentifierPart = false;
+        NormalizedNodeContext currentNodeCtx = this.currentNodeCtx;
+        NormalizedNode<?, ?> currentNode = null;
+        for (String pathComponent : pathComponents) {
+            final Matcher matcher = NODE_IDENTIFIER_PATTERN.matcher(pathComponent);
+            if (UP_ONE_LEVEL.equals(pathComponent)) {
+                checkValid(!inNodeIdentifierPart, "Up-one-level expression cannot follow concrete path component.");
+                currentNodeCtx = currentNodeCtx.getParent();
+                currentNode = currentNodeCtx.getNode();
+                offset += UP_ONE_LEVEL.length() + 1;
+            } else if (matcher.matches()) {
+                inNodeIdentifierPart = true;
+                if (currentNode != null && currentNode instanceof DataContainerNode) {
+                    final DataContainerNode dcn = (DataContainerNode) currentNode;
+                    final Optional<NormalizedNode<?, ?>> possibleChild = dcn.getChild(new NodeIdentifier(nextQName()));
+                    currentNode = possibleChild.isPresent() ? possibleChild.get() : null;
+                }
+            } else {
+                throw new IllegalArgumentException(String.format(
+                        "Could not parse leafref path '%s'. Offset: %s : Reason: Malformed path component: '%s'.",
+                        xPathString, offset, pathComponent));
+            }
+        }
+
+        if (currentNode != null && currentNode instanceof LeafNode) {
+            return currentNode.getValue();
+        }
+
+        throw new IllegalArgumentException("Could not resolve current function path value.");
+
+    }
+
+    /**
+     *
+     * Returns following QName and sets offset to end of QName.
+     *
+     * @return following QName.
+     */
+    private QName nextQName() {
+        // Consume prefix or identifier
+        final String maybePrefix = nextIdentifier();
+        final String prefix, localName;
+        if (!allCharactersConsumed() && COLON == currentChar()) {
+            // previous token is prefix;
+            prefix = maybePrefix;
+            skipCurrentChar();
+            localName = nextIdentifier();
+        } else {
+            prefix = "";
+            localName = maybePrefix;
+        }
+        return createQName(prefix, localName);
+    }
+
+    /**
+     * Returns true if all characters from input string
+     * were consumed.
+     *
+     * @return true if all characters from input string
+     * were consumed.
+     */
+    private boolean allCharactersConsumed() {
+        return offset == xPathString.length();
+    }
+
+    private QName createQName(final String prefix, final String localName) {
+        final Module module = schemaContext.findModuleByNamespaceAndRevision(schemaNode.getQName().getNamespace(),
+                schemaNode.getQName().getRevision());
+        if (prefix.isEmpty() || module.getPrefix().equals(prefix)) {
+            return QName.create(module.getQNameModule(), localName);
+        }
+
+        for (final ModuleImport moduleImport : module.getImports()) {
+            if (prefix.equals(moduleImport.getPrefix())) {
+                final Module importedModule = schemaContext.findModuleByName(moduleImport.getModuleName(),
+                        moduleImport.getRevision());
+                return QName.create(importedModule.getQNameModule(),localName);
+            }
+        }
+
+        throw new IllegalArgumentException(String.format("Failed to lookup a module for prefix %s", prefix));
+    }
+
+    /**
+     *
+     * Skips current char if it equals expected otherwise fails parsing.
+     *
+     * @param expected Expected character
+     * @param errorMsg Error message if {@link #currentChar()} does not match expected.
+     */
+    private void checkCurrentAndSkip(final char expected, final String errorMsg) {
+        checkValid(expected == currentChar(), errorMsg);
+        offset++;
+    }
+
+    /**
+     *
+     * Fails parsing if condition is not met.
+     *
+     * In case of error provides pointer to failed leafref,
+     * offset on which failure occured with explanation.
+     *
+     * @param condition Fails parsing if {@code condition} is false
+     * @param errorMsg Error message which will be provided to user.
+     * @param attributes
+     */
+    private void checkValid(final boolean condition, final String errorMsg, final Object... attributes) {
+        if (!condition) {
+            throw new IllegalArgumentException(String.format(
+                    "Could not parse leafref path '%s'. Offset: %s : Reason: %s", xPathString, offset,
+                    String.format(errorMsg, attributes)));
+        }
+    }
+
+    /**
+     * Returns character at current offset.
+     *
+     * @return character at current offset.
+     */
+    private char currentChar() {
+        return xPathString.charAt(offset);
+    }
+
+    /**
+     * Increases processing offset by 1
+     */
+    private void skipCurrentChar() {
+        offset++;
+    }
+
+    /**
+     * Skip whitespace characters, sets offset to first following
+     * non-whitespace character.
+     */
+    private void skipWhitespaces() {
+        nextSequenceEnd(WSP);
+    }
+
+    /**
+     * Returns a string which matches IDENTIFIER YANG ABNF token
+     * and sets processing offset after the end of identifier.
+     *
+     * @return string which matches IDENTIFIER YANG ABNF token
+     */
+    private String nextIdentifier() {
+        int start = offset;
+        checkValid(IDENTIFIER_FIRST_CHAR.matches(currentChar()), "Identifier must start with character from set 'a-zA-Z_'");
+        nextSequenceEnd(IDENTIFIER);
+        return xPathString.substring(start, offset);
+    }
+
+    private void nextSequenceEnd(final CharMatcher matcher) {
+        while (!allCharactersConsumed() && matcher.matches(xPathString.charAt(offset))) {
+            offset++;
+        }
+    }
+}
\ No newline at end of file
index cff335e28828b8ea1731d6024ff2408e1b3b9e0d..efba94a64cae69cc8fc9a0721cdedfcfa906057c 100644 (file)
@@ -17,6 +17,7 @@ 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.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
 final class NormalizedNodeContextSupport extends ContextSupport {
     private static final long serialVersionUID = 1L;
@@ -46,6 +47,10 @@ final class NormalizedNodeContextSupport extends ContextSupport {
         return result;
     }
 
+    SchemaContext getSchemaContext() {
+        return getNavigator().getSchemaContext();
+    }
+
     @Override
     public NormalizedNodeNavigator getNavigator() {
         return (NormalizedNodeNavigator) super.getNavigator();
index 0954ecfb922b549273f9a866096addd90b087269..78e61844d537c6f1355ddb469512fe04631c8c98 100644 (file)
@@ -20,6 +20,7 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.NoSuchElementException;
 import java.util.Set;
+import javax.annotation.Nonnull;
 import org.jaxen.DefaultNavigator;
 import org.jaxen.NamedAccessNavigator;
 import org.jaxen.Navigator;
@@ -36,6 +37,7 @@ 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.model.api.SchemaContext;
 
 /**
  * A {@link Navigator} implementation for YANG XPaths instantiated on a particular root {@link NormalizedNode}.
@@ -313,6 +315,11 @@ final class NormalizedNodeNavigator extends DefaultNavigator implements NamedAcc
         return document.getRootNode();
     }
 
+    @Nonnull
+    SchemaContext getSchemaContext() {
+        return document.getSchemaContext();
+    }
+
     private static final class NormalizedNodeContextIterator extends UnmodifiableIterator<NormalizedNodeContext> {
         private NormalizedNodeContext next;
 
index 87c8976740f3adb903550427606ad91343f083ec..a335a5099f4d2e3dbdcb9acb4d4fa82bc5944548 100644 (file)
@@ -7,17 +7,56 @@
  */
 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 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.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
@@ -30,6 +69,391 @@ final class YangFunctionContext implements FunctionContext {
         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();
+            if (xPath.isAbsolute()) {
+                final NormalizedNode<?, ?> referencedNode = getNodeReferencedByAbsoluteLeafref(
+                        xPath, currentNodeContext, schemaContext, correspondingSchemaNode);
+                if (referencedNode.getValue().equals(nodeValue)) {
+                    return referencedNode;
+                }
+            } else {
+                final NormalizedNode<?, ?> referencedNode = getNodeReferencedByRelativeLeafref(
+                        xPath, currentNodeContext, schemaContext, correspondingSchemaNode);
+                if (referencedNode.getValue().equals(nodeValue)) {
+                    return referencedNode;
+                }
+            }
+        }
+
+        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;
+        }
+
+        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;
+        }
+
+        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;
+    }
+
+    // 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("Cannot resolve prefix '%s' from identity '%s'.");
+        }
+
+        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);
+
+        if (!(correspondingSchemaNode.getType() instanceof EnumTypeDefinition)) {
+            return DOUBLE_NAN;
+        }
+
+        if (!(currentNodeContext.getNode().getValue() instanceof String)) {
+            return DOUBLE_NAN;
+        }
+
+        final EnumTypeDefinition enumerationType = (EnumTypeDefinition) correspondingSchemaNode.getType();
+        final String enumName = (String) currentNodeContext.getNode().getValue();
+
+        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;
+        }
+
+        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();
 
@@ -41,10 +465,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);
     }
 }
diff --git a/yang/yang-data-jaxen/src/test/java/org/opendaylight/yangtools/yang/data/jaxen/YangXPathFunctionsTest.java b/yang/yang-data-jaxen/src/test/java/org/opendaylight/yangtools/yang/data/jaxen/YangXPathFunctionsTest.java
new file mode 100644 (file)
index 0000000..a79ab08
--- /dev/null
@@ -0,0 +1,371 @@
+/*
+ * Copyright (c) 2017 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.yangtools.yang.data.jaxen;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import java.net.URI;
+import java.util.Map;
+import java.util.Set;
+import org.jaxen.Context;
+import org.jaxen.Function;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+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.xpath.XPathDocument;
+import org.opendaylight.yangtools.yang.data.api.schema.xpath.XPathSchemaContext;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+
+public class YangXPathFunctionsTest {
+
+    private static JaxenSchemaContextFactory jaxenSchemaContextFactory;
+
+    @BeforeClass
+    public static void setup() {
+        jaxenSchemaContextFactory = new JaxenSchemaContextFactory();
+    }
+
+    @Test
+    public void testRematchFunction() throws Exception {
+        // re-match() uses regex processing from yang-parser-impl which has been thoroughly tested within
+        // the Bug5410Test unit test class, so here is just a basic test
+        final YangFunctionContext yangFunctionContext = YangFunctionContext.getInstance();
+        final Function rematchFunction = yangFunctionContext.getFunction(null, null, "re-match");
+
+        final Context mockedContext = mock(Context.class);
+
+        boolean rematchResult = (boolean) rematchFunction.call(mockedContext, ImmutableList.of("abbc", "[abc]{1,4}"));
+        assertTrue(rematchResult);
+        rematchResult = (boolean) rematchFunction.call(mockedContext, ImmutableList.of("abbcc", "[abc]{1,4}"));
+        assertFalse(rematchResult);
+    }
+
+    @Test
+    public void testDerefFunctionForInstanceIdentifier() throws Exception {
+        final QNameModule fooModule = QNameModule.create(URI.create("foo-ns"),
+                SimpleDateFormatUtil.getRevisionFormat().parse("2017-04-03"));
+        final QName myContainer = QName.create(fooModule, "my-container");
+        final QName myList = QName.create(fooModule, "my-list");
+        final QName keyLeafA = QName.create(fooModule, "key-leaf-a");
+        final QName keyLeafB = QName.create(fooModule, "key-leaf-b");
+        final QName iidLeaf = QName.create(fooModule, "iid-leaf");
+        final QName referencedLeaf = QName.create(fooModule, "referenced-leaf");
+
+        final Map<QName, Object> keyValues = ImmutableMap.of(keyLeafA, "key-value-a", keyLeafB, "key-value-b");
+        final YangInstanceIdentifier iidPath = YangInstanceIdentifier.of(myContainer).node(myList)
+                .node(new NodeIdentifierWithPredicates(myList, keyValues)).node(referencedLeaf);
+
+        final LeafNode<?> iidLeafNode = Builders.leafBuilder().withNodeIdentifier(new NodeIdentifier(iidLeaf))
+                .withValue(iidPath).build();
+        final LeafNode<?> referencedLeafNode = Builders.leafBuilder().withNodeIdentifier(new NodeIdentifier(referencedLeaf))
+                .withValue("referenced-leaf-node-value").build();
+
+        final MapNode myListNode = Builders.mapBuilder().withNodeIdentifier(new NodeIdentifier(myList))
+                .withChild(Builders.mapEntryBuilder().withNodeIdentifier(
+                        new NodeIdentifierWithPredicates(myList, keyValues))
+                        .withChild(iidLeafNode)
+                        .withChild(referencedLeafNode).build())
+                .build();
+
+        final ContainerNode myContainerNode = Builders.containerBuilder().withNodeIdentifier(
+                new NodeIdentifier(myContainer)).withChild(myListNode).build();
+
+        final SchemaContext schemaContext = YangParserTestUtils.parseYangSource(
+                "/yang-xpath-functions-test/deref-function-iid/foo.yang");
+        assertNotNull(schemaContext);
+
+        final XPathSchemaContext jaxenSchemaContext = jaxenSchemaContextFactory.createContext(schemaContext);
+        final XPathDocument jaxenDocument = jaxenSchemaContext.createDocument(myContainerNode);
+
+        final BiMap<String, QNameModule> converterBiMap = HashBiMap.create();
+        converterBiMap.put("foo-prefix", fooModule);
+
+        final NormalizedNodeContextSupport normalizedNodeContextSupport = NormalizedNodeContextSupport.create(
+                (JaxenDocument) jaxenDocument, Maps.asConverter(converterBiMap));
+
+        final YangInstanceIdentifier path = YangInstanceIdentifier.of(myList)
+                .node(new NodeIdentifierWithPredicates(myList, keyValues)).node(iidLeaf);
+        final NormalizedNodeContext normalizedNodeContext = normalizedNodeContextSupport.createContext(path);
+
+        final Function derefFunction = normalizedNodeContextSupport.getFunctionContext()
+                .getFunction(null, null, "deref");
+        final Object derefResult = derefFunction.call(normalizedNodeContext, ImmutableList.of());
+        assertNotNull(derefResult);
+        assertTrue(derefResult instanceof NormalizedNode<?, ?>);
+        assertSame(referencedLeafNode, derefResult);
+    }
+
+    @Test
+    public void testDerefFunctionForLeafref() throws Exception {
+        final QNameModule fooModule = QNameModule.create(URI.create("foo-ns"),
+                SimpleDateFormatUtil.getRevisionFormat().parse("2017-04-03"));
+        final QName myContainer = QName.create(fooModule, "my-container");
+        final QName myInnerContainer = QName.create(fooModule, "my-inner-container");
+        final QName myList = QName.create(fooModule, "my-list");
+        final QName keyLeafA = QName.create(fooModule, "key-leaf-a");
+        final QName keyLeafB = QName.create(fooModule, "key-leaf-b");
+        final QName absLeafrefLeaf = QName.create(fooModule, "abs-leafref-leaf");
+        final QName relLeafrefLeaf = QName.create(fooModule, "rel-leafref-leaf");
+        final QName ordinaryLeafA = QName.create(fooModule, "ordinary-leaf-a");
+        final QName ordinaryLeafB = QName.create(fooModule, "ordinary-leaf-b");
+        final QName referencedLeaf = QName.create(fooModule, "referenced-leaf");
+
+        final Map<QName, Object> keyValues = ImmutableMap.of(keyLeafA, "value-a", keyLeafB, "value-b");
+
+        final LeafNode<?> absLeafrefNode = Builders.leafBuilder().withNodeIdentifier(new NodeIdentifier(absLeafrefLeaf))
+                .withValue("referenced-leaf-node-value").build();
+        final LeafNode<?> relLeafrefNode = Builders.leafBuilder().withNodeIdentifier(new NodeIdentifier(relLeafrefLeaf))
+                .withValue("referenced-leaf-node-value").build();
+        final LeafNode<?> ordinaryLeafANode = Builders.leafBuilder().withNodeIdentifier(
+                new NodeIdentifier(ordinaryLeafA)).withValue("value-a").build();
+        final LeafNode<?> ordinaryLeafBNode = Builders.leafBuilder().withNodeIdentifier(
+                new NodeIdentifier(ordinaryLeafB)).withValue("value-b").build();
+
+        final LeafNode<?> referencedLeafNode = Builders.leafBuilder().withNodeIdentifier(
+                new NodeIdentifier(referencedLeaf)).withValue("referenced-leaf-node-value").build();
+
+        final MapNode myListNode = Builders.mapBuilder().withNodeIdentifier(new NodeIdentifier(myList))
+                .withChild(Builders.mapEntryBuilder().withNodeIdentifier(
+                        new NodeIdentifierWithPredicates(myList, keyValues))
+                        .withChild(referencedLeafNode).build())
+                .build();
+
+        final ContainerNode myInnerContainerNode = Builders.containerBuilder().withNodeIdentifier(
+                new NodeIdentifier(myInnerContainer))
+                .withChild(absLeafrefNode)
+                .withChild(relLeafrefNode)
+                .withChild(ordinaryLeafANode)
+                .withChild(ordinaryLeafBNode).build();
+
+        final ContainerNode myContainerNode = Builders.containerBuilder().withNodeIdentifier(
+                new NodeIdentifier(myContainer))
+                .withChild(myListNode)
+                .withChild(myInnerContainerNode).build();
+
+        final SchemaContext schemaContext = YangParserTestUtils.parseYangSource(
+                "/yang-xpath-functions-test/deref-function-leafref/foo.yang");
+        assertNotNull(schemaContext);
+
+        final XPathSchemaContext jaxenSchemaContext = jaxenSchemaContextFactory.createContext(schemaContext);
+        final XPathDocument jaxenDocument = jaxenSchemaContext.createDocument(myContainerNode);
+
+        final BiMap<String, QNameModule> converterBiMap = HashBiMap.create();
+        converterBiMap.put("foo-prefix", fooModule);
+
+        final NormalizedNodeContextSupport normalizedNodeContextSupport = NormalizedNodeContextSupport.create(
+                (JaxenDocument) jaxenDocument, Maps.asConverter(converterBiMap));
+
+        final YangInstanceIdentifier absLeafrefPath = YangInstanceIdentifier.of(myInnerContainer).node(absLeafrefLeaf);
+        NormalizedNodeContext normalizedNodeContext = normalizedNodeContextSupport.createContext(absLeafrefPath);
+
+        final Function derefFunction = normalizedNodeContextSupport.getFunctionContext()
+                .getFunction(null, null, "deref");
+        Object derefResult = derefFunction.call(normalizedNodeContext, ImmutableList.of());
+        assertNotNull(derefResult);
+        assertTrue(derefResult instanceof NormalizedNode<?, ?>);
+        assertSame(referencedLeafNode, derefResult);
+
+        final YangInstanceIdentifier relLeafrefPath = YangInstanceIdentifier.of(myInnerContainer).node(relLeafrefLeaf);
+        normalizedNodeContext = normalizedNodeContextSupport.createContext(relLeafrefPath);
+
+        derefResult = derefFunction.call(normalizedNodeContext, ImmutableList.of());
+        assertNotNull(derefResult);
+        assertTrue(derefResult instanceof NormalizedNode<?, ?>);
+        assertSame(referencedLeafNode, derefResult);
+    }
+
+    @Test
+    public void testDerivedFromFunction() throws Exception {
+        // also includes test for derived-from-or-self function
+        final QNameModule barModule = QNameModule.create(URI.create("bar-ns"),
+                SimpleDateFormatUtil.getRevisionFormat().parse("2017-04-03"));
+        final QName myContainer = QName.create(barModule, "my-container");
+        final QName myList = QName.create(barModule, "my-list");
+        final QName keyLeaf = QName.create(barModule, "key-leaf");
+        final QName idrefLeaf = QName.create(barModule, "idref-leaf");
+        final QName idC2Identity = QName.create(barModule, "id-c2");
+
+        final LeafNode<?> idrefLeafNode = Builders.leafBuilder().withNodeIdentifier(new NodeIdentifier(idrefLeaf))
+                .withValue(idC2Identity).build();
+
+        final MapNode myListNode = Builders.mapBuilder().withNodeIdentifier(new NodeIdentifier(myList))
+                .withChild(Builders.mapEntryBuilder().withNodeIdentifier(
+                        new NodeIdentifierWithPredicates(myList, keyLeaf, "key-value"))
+                        .withChild(idrefLeafNode).build()).build();
+
+        final ContainerNode myContainerNode = Builders.containerBuilder().withNodeIdentifier(
+                new NodeIdentifier(myContainer)).withChild(myListNode).build();
+
+        final SchemaContext schemaContext = YangParserTestUtils.parseYangSources(
+                "/yang-xpath-functions-test/derived-from-function");
+        assertNotNull(schemaContext);
+
+        final XPathSchemaContext jaxenSchemaContext = jaxenSchemaContextFactory.createContext(schemaContext);
+        final XPathDocument jaxenDocument = jaxenSchemaContext.createDocument(myContainerNode);
+
+        final BiMap<String, QNameModule> converterBiMap = HashBiMap.create();
+        converterBiMap.put("bar-prefix", barModule);
+
+        final NormalizedNodeContextSupport normalizedNodeContextSupport = NormalizedNodeContextSupport.create(
+                (JaxenDocument) jaxenDocument, Maps.asConverter(converterBiMap));
+
+        final ImmutableMap.Builder<QName, Object> builder = ImmutableMap.builder();
+        final ImmutableMap<QName, Object> keys = builder.put(keyLeaf, "key-value").build();
+
+        final YangInstanceIdentifier path = YangInstanceIdentifier.of(myList)
+                .node(new NodeIdentifierWithPredicates(myList, keys)).node(idrefLeaf);
+        final NormalizedNodeContext normalizedNodeContext = normalizedNodeContextSupport.createContext(path);
+
+        final Function derivedFromFunction = normalizedNodeContextSupport.getFunctionContext()
+                .getFunction(null, null, "derived-from");
+
+        assertTrue(getDerivedFromResult(derivedFromFunction, normalizedNodeContext, "foo-prefix:id-a3"));
+        assertTrue(getDerivedFromResult(derivedFromFunction, normalizedNodeContext, "foo-prefix:id-a4"));
+        assertTrue(getDerivedFromResult(derivedFromFunction, normalizedNodeContext, "foo-prefix:id-b2"));
+        assertTrue(getDerivedFromResult(derivedFromFunction, normalizedNodeContext, "bar-prefix:id-b3"));
+        assertTrue(getDerivedFromResult(derivedFromFunction, normalizedNodeContext, "id-b4"));
+
+        assertFalse(getDerivedFromResult(derivedFromFunction, normalizedNodeContext, "foo-prefix:id-a1"));
+        assertFalse(getDerivedFromResult(derivedFromFunction, normalizedNodeContext, "foo-prefix:id-a2"));
+        assertFalse(getDerivedFromResult(derivedFromFunction, normalizedNodeContext, "foo-prefix:id-b1"));
+        assertFalse(getDerivedFromResult(derivedFromFunction, normalizedNodeContext, "foo-prefix:id-c1"));
+        assertFalse(getDerivedFromResult(derivedFromFunction, normalizedNodeContext, "bar-prefix:id-c2"));
+
+        final Function derivedFromOrSelfFunction = normalizedNodeContextSupport.getFunctionContext()
+                .getFunction(null, null, "derived-from-or-self");
+        assertTrue(getDerivedFromResult(derivedFromOrSelfFunction, normalizedNodeContext, "bar-prefix:id-c2"));
+    }
+
+
+
+    @Test
+    public void testEnumValueFunction() throws Exception {
+        final QNameModule fooModule = QNameModule.create(URI.create("foo-ns"),
+                SimpleDateFormatUtil.getRevisionFormat().parse("2017-04-03"));
+        final QName myContainer = QName.create(fooModule, "my-container");
+        final QName alarm = QName.create(fooModule, "alarm");
+        final QName severity = QName.create(fooModule, "severity");
+        final QName ordinaryLeaf = QName.create(fooModule, "ordinary-leaf");
+
+        final LeafNode<?> ordinaryLeafNode = Builders.leafBuilder().withNodeIdentifier(new NodeIdentifier(ordinaryLeaf))
+                .withValue("test-value").build();
+
+        final MapNode alarmListNode = Builders.mapBuilder().withNodeIdentifier(new NodeIdentifier(alarm))
+                .withChild(Builders.mapEntryBuilder().withNodeIdentifier(
+                        new NodeIdentifierWithPredicates(alarm, severity, "major"))
+                        .withChild(ordinaryLeafNode).build()).build();
+
+        final ContainerNode myContainerNode = Builders.containerBuilder().withNodeIdentifier(
+                new NodeIdentifier(myContainer)).withChild(alarmListNode).build();
+
+        final SchemaContext schemaContext = YangParserTestUtils.parseYangSource(
+                "/yang-xpath-functions-test/enum-value-function/foo.yang");
+        assertNotNull(schemaContext);
+
+        final XPathSchemaContext jaxenSchemaContext = jaxenSchemaContextFactory.createContext(schemaContext);
+        final XPathDocument jaxenDocument = jaxenSchemaContext.createDocument(myContainerNode);
+
+        final BiMap<String, QNameModule> converterBiMap = HashBiMap.create();
+        converterBiMap.put("foo-prefix", fooModule);
+
+        final NormalizedNodeContextSupport normalizedNodeContextSupport = NormalizedNodeContextSupport.create(
+                (JaxenDocument) jaxenDocument, Maps.asConverter(converterBiMap));
+
+        final ImmutableMap.Builder<QName, Object> builder = ImmutableMap.builder();
+        final ImmutableMap<QName, Object> keys = builder.put(severity, "major").build();
+
+        final YangInstanceIdentifier path = YangInstanceIdentifier.of(alarm)
+                .node(new NodeIdentifierWithPredicates(alarm, keys)).node(severity);
+        final NormalizedNodeContext normalizedNodeContext = normalizedNodeContextSupport.createContext(path);
+
+        final Function enumValueFunction = normalizedNodeContextSupport.getFunctionContext()
+                .getFunction(null, null, "enum-value");
+        final int enumValueResult = (int) enumValueFunction.call(normalizedNodeContext, ImmutableList.of());
+        assertEquals(5, enumValueResult);
+    }
+
+    @Test
+    public void testBitIsSetFunction() throws Exception {
+        final QNameModule fooModule = QNameModule.create(URI.create("foo-ns"),
+                SimpleDateFormatUtil.getRevisionFormat().parse("2017-04-03"));
+        final QName myContainer = QName.create(fooModule, "my-container");
+        final QName myList = QName.create(fooModule, "my-list");
+        final QName flags = QName.create(fooModule, "flags");
+        final QName ordinaryLeaf = QName.create(fooModule, "ordinary-leaf");
+
+        final LeafNode<?> ordinaryLeafNode = Builders.leafBuilder().withNodeIdentifier(new NodeIdentifier(ordinaryLeaf))
+                .withValue("test-value").build();
+
+        final Set<String> setOfBits = ImmutableSet.of("UP", "PROMISCUOUS");
+
+        final MapNode myListNode = Builders.mapBuilder().withNodeIdentifier(new NodeIdentifier(myList))
+                .withChild(Builders.mapEntryBuilder().withNodeIdentifier(
+                        new NodeIdentifierWithPredicates(myList, flags, setOfBits))
+                        .withChild(ordinaryLeafNode).build()).build();
+
+        final ContainerNode myContainerNode = Builders.containerBuilder().withNodeIdentifier(
+                new NodeIdentifier(myContainer)).withChild(myListNode).build();
+
+        final SchemaContext schemaContext = YangParserTestUtils.parseYangSource(
+                "/yang-xpath-functions-test/bit-is-set-function/foo.yang");
+        assertNotNull(schemaContext);
+
+        final XPathSchemaContext jaxenSchemaContext = jaxenSchemaContextFactory.createContext(schemaContext);
+        final XPathDocument jaxenDocument = jaxenSchemaContext.createDocument(myContainerNode);
+
+        final BiMap<String, QNameModule> converterBiMap = HashBiMap.create();
+        converterBiMap.put("foo-prefix", fooModule);
+
+        final NormalizedNodeContextSupport normalizedNodeContextSupport = NormalizedNodeContextSupport.create(
+                (JaxenDocument) jaxenDocument, Maps.asConverter(converterBiMap));
+
+        final ImmutableMap.Builder<QName, Object> builder = ImmutableMap.builder();
+        final ImmutableMap<QName, Object> keys = builder.put(flags, setOfBits).build();
+
+        final YangInstanceIdentifier path = YangInstanceIdentifier.of(myList)
+                .node(new NodeIdentifierWithPredicates(myList, keys)).node(flags);
+        final NormalizedNodeContext normalizedNodeContext = normalizedNodeContextSupport.createContext(path);
+
+        final Function bitIsSetFunction = normalizedNodeContextSupport.getFunctionContext()
+                .getFunction(null, null, "bit-is-set");
+        boolean bitIsSetResult = (boolean) bitIsSetFunction.call(normalizedNodeContext, ImmutableList.of("UP"));
+        assertTrue(bitIsSetResult);
+        bitIsSetResult = (boolean) bitIsSetFunction.call(normalizedNodeContext, ImmutableList.of("PROMISCUOUS"));
+        assertTrue(bitIsSetResult);
+        bitIsSetResult = (boolean) bitIsSetFunction.call(normalizedNodeContext, ImmutableList.of("DISABLED"));
+        assertFalse(bitIsSetResult);
+    }
+
+    private static boolean getDerivedFromResult(final Function derivedFromFunction, final NormalizedNodeContext nnCtx,
+            final String identityArg) throws Exception {
+        return (boolean) derivedFromFunction.call(nnCtx, ImmutableList.of(identityArg));
+    }
+}
diff --git a/yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/bit-is-set-function/foo.yang b/yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/bit-is-set-function/foo.yang
new file mode 100644 (file)
index 0000000..3d18861
--- /dev/null
@@ -0,0 +1,25 @@
+module foo {
+    namespace foo-ns;
+    prefix foo-prefix;
+    yang-version 1.1;
+
+    revision 2017-04-03;
+
+    container my-container {
+        list my-list {
+            key flags;
+
+            leaf flags {
+                type bits {
+                    bit UP;
+                    bit PROMISCUOUS;
+                    bit DISABLED;
+                }
+            }
+
+            leaf ordinary-leaf {
+                type string;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/deref-function-iid/foo.yang b/yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/deref-function-iid/foo.yang
new file mode 100644 (file)
index 0000000..7dfda62
--- /dev/null
@@ -0,0 +1,29 @@
+module foo {
+    namespace foo-ns;
+    prefix foo-prefix;
+    yang-version 1.1;
+
+    revision 2017-04-03;
+
+    container my-container {
+        list my-list {
+            key "key-leaf-a key-leaf-b";
+
+            leaf key-leaf-a {
+                type string;
+            }
+
+            leaf key-leaf-b {
+                type string;
+            }
+
+            leaf iid-leaf {
+                type instance-identifier;
+            }
+
+            leaf referenced-leaf {
+                type string;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/deref-function-leafref/foo.yang b/yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/deref-function-leafref/foo.yang
new file mode 100644 (file)
index 0000000..a5c771a
--- /dev/null
@@ -0,0 +1,49 @@
+module foo {
+    namespace foo-ns;
+    prefix foo-prefix;
+    yang-version 1.1;
+
+    revision 2017-04-03;
+
+    container my-container {
+        list my-list {
+            key "key-leaf-a key-leaf-b";
+
+            leaf key-leaf-a {
+                type string;
+            }
+
+            leaf key-leaf-b {
+                type string;
+            }
+
+            leaf referenced-leaf {
+                type string;
+            }
+        }
+
+        container my-inner-container {
+            leaf abs-leafref-leaf {
+                type leafref {
+                    path "/my-container/my-list[key-leaf-a=current()/../ordinary-leaf-a]" +
+                        "[key-leaf-b=current()/../ordinary-leaf-b]/referenced-leaf";
+                }
+            }
+
+            leaf rel-leafref-leaf {
+                type leafref {
+                    path "../../my-list[key-leaf-a=current()/../ordinary-leaf-a]" +
+                        "[key-leaf-b=current()/../ordinary-leaf-b]/referenced-leaf";
+                }
+            }
+
+            leaf ordinary-leaf-a {
+                type string;
+            }
+
+            leaf ordinary-leaf-b {
+                type string;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/derived-from-function/bar.yang b/yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/derived-from-function/bar.yang
new file mode 100644 (file)
index 0000000..1532fc7
--- /dev/null
@@ -0,0 +1,37 @@
+module bar {
+    namespace bar-ns;
+    prefix bar-prefix;
+    yang-version 1.1;
+
+    import foo {
+        prefix foo-prefix;
+        revision-date 2017-04-03;
+    }
+
+    revision 2017-04-03;
+
+    identity id-b3;
+    identity id-b4;
+
+    identity id-c2 {
+        base foo-prefix:id-b2;
+        base id-b3;
+        base id-b4;
+    }
+
+    container my-container {
+        list my-list {
+            key key-leaf;
+
+            leaf key-leaf {
+                type string;
+            }
+
+            leaf idref-leaf {
+                type identityref {
+                    base foo-prefix:id-a3;
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/derived-from-function/foo.yang b/yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/derived-from-function/foo.yang
new file mode 100644 (file)
index 0000000..7fec0c6
--- /dev/null
@@ -0,0 +1,29 @@
+module foo {
+    namespace foo-ns;
+    prefix foo-prefix;
+    yang-version 1.1;
+
+    revision 2017-04-03;
+
+    identity id-a1;
+    identity id-a2;
+    identity id-a3;
+    identity id-a4;
+
+    identity id-b1 {
+        base id-a1;
+        base id-a2;
+    }
+
+    identity id-b2 {
+        base id-a3;
+        base id-a4;
+    }
+
+    identity id-c1 {
+        base id-b1;
+        base id-b2;
+    }
+
+    container my-container {}
+}
\ No newline at end of file
diff --git a/yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/enum-value-function/foo.yang b/yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/enum-value-function/foo.yang
new file mode 100644 (file)
index 0000000..40dc2fd
--- /dev/null
@@ -0,0 +1,40 @@
+module foo {
+    namespace foo-ns;
+    prefix foo-prefix;
+    yang-version 1.1;
+
+    revision 2017-04-03;
+
+    container my-container {
+        list alarm {
+            key severity;
+
+            leaf ordinary-leaf {
+                type string;
+            }
+
+            leaf severity {
+                type enumeration {
+                    enum cleared {
+                        value 1;
+                    }
+                    enum indeterminate {
+                        value 2;
+                    }
+                    enum minor {
+                        value 3;
+                    }
+                    enum warning {
+                        value 4;
+                    }
+                    enum major {
+                        value 5;
+                    }
+                    enum critical {
+                        value 6;
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/RegexUtils.java b/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/RegexUtils.java
new file mode 100644 (file)
index 0000000..862d7e5
--- /dev/null
@@ -0,0 +1,330 @@
+/*
+ * Copyright (c) 2017 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.yangtools.yang.model.util;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utilities for converting YANG XSD regexes into Java-compatible regexes
+ */
+public final class RegexUtils {
+    private static final Logger LOG = LoggerFactory.getLogger(RegexUtils.class);
+    private static final Pattern BETWEEN_CURLY_BRACES_PATTERN = Pattern.compile("\\{(.+?)\\}");
+    private static final Set<String> JAVA_UNICODE_BLOCKS = ImmutableSet.<String>builder()
+            .add("AegeanNumbers")
+            .add("AlchemicalSymbols")
+            .add("AlphabeticPresentationForms")
+            .add("AncientGreekMusicalNotation")
+            .add("AncientGreekNumbers")
+            .add("AncientSymbols")
+            .add("Arabic")
+            .add("ArabicPresentationForms-A")
+            .add("ArabicPresentationForms-B")
+            .add("ArabicSupplement")
+            .add("Armenian")
+            .add("Arrows")
+            .add("Avestan")
+            .add("Balinese")
+            .add("Bamum")
+            .add("BamumSupplement")
+            .add("BasicLatin")
+            .add("Batak")
+            .add("Bengali")
+            .add("BlockElements")
+            .add("Bopomofo")
+            .add("BopomofoExtended")
+            .add("BoxDrawing")
+            .add("Brahmi")
+            .add("BraillePatterns")
+            .add("Buginese")
+            .add("Buhid")
+            .add("ByzantineMusicalSymbols")
+            .add("Carian")
+            .add("Cham")
+            .add("Cherokee")
+            .add("CJKCompatibility")
+            .add("CJKCompatibilityForms")
+            .add("CJKCompatibilityIdeographs")
+            .add("CJKCompatibilityIdeographsSupplement")
+            .add("CJKRadicalsSupplement")
+            .add("CJKStrokes")
+            .add("CJKSymbolsandPunctuation")
+            .add("CJKUnifiedIdeographs")
+            .add("CJKUnifiedIdeographsExtensionA")
+            .add("CJKUnifiedIdeographsExtensionB")
+            .add("CJKUnifiedIdeographsExtensionC")
+            .add("CJKUnifiedIdeographsExtensionD")
+            .add("CombiningDiacriticalMarks")
+            .add("CombiningDiacriticalMarksSupplement")
+            .add("CombiningHalfMarks")
+            .add("CombiningDiacriticalMarksforSymbols")
+            .add("CommonIndicNumberForms")
+            .add("ControlPictures")
+            .add("Coptic")
+            .add("CountingRodNumerals")
+            .add("Cuneiform")
+            .add("CuneiformNumbersandPunctuation")
+            .add("CurrencySymbols")
+            .add("CypriotSyllabary")
+            .add("Cyrillic")
+            .add("CyrillicExtended-A")
+            .add("CyrillicExtended-B")
+            .add("CyrillicSupplementary")
+            .add("Deseret")
+            .add("Devanagari")
+            .add("DevanagariExtended")
+            .add("Dingbats")
+            .add("DominoTiles")
+            .add("EgyptianHieroglyphs")
+            .add("Emoticons")
+            .add("EnclosedAlphanumericSupplement")
+            .add("EnclosedAlphanumerics")
+            .add("EnclosedCJKLettersandMonths")
+            .add("EnclosedIdeographicSupplement")
+            .add("Ethiopic")
+            .add("EthiopicExtended")
+            .add("EthiopicExtended-A")
+            .add("EthiopicSupplement")
+            .add("GeneralPunctuation")
+            .add("GeometricShapes")
+            .add("Georgian")
+            .add("GeorgianSupplement")
+            .add("Glagolitic")
+            .add("Gothic")
+            .add("GreekandCoptic")
+            .add("GreekExtended")
+            .add("Gujarati")
+            .add("Gurmukhi")
+            .add("HalfwidthandFullwidthForms")
+            .add("HangulCompatibilityJamo")
+            .add("HangulJamo")
+            .add("HangulJamoExtended-A")
+            .add("HangulJamoExtended-B")
+            .add("HangulSyllables")
+            .add("Hanunoo")
+            .add("Hebrew")
+            .add("HighPrivateUseSurrogates")
+            .add("HighSurrogates")
+            .add("Hiragana")
+            .add("IdeographicDescriptionCharacters")
+            .add("ImperialAramaic")
+            .add("InscriptionalPahlavi")
+            .add("InscriptionalParthian")
+            .add("IPAExtensions")
+            .add("Javanese")
+            .add("Kaithi")
+            .add("KanaSupplement")
+            .add("Kanbun")
+            .add("Kangxi Radicals")
+            .add("Kannada")
+            .add("Katakana")
+            .add("KatakanaPhoneticExtensions")
+            .add("KayahLi")
+            .add("Kharoshthi")
+            .add("Khmer")
+            .add("KhmerSymbols")
+            .add("Lao")
+            .add("Latin-1Supplement")
+            .add("LatinExtended-A")
+            .add("LatinExtendedAdditional")
+            .add("LatinExtended-B")
+            .add("LatinExtended-C")
+            .add("LatinExtended-D")
+            .add("Lepcha")
+            .add("LetterlikeSymbols")
+            .add("Limbu")
+            .add("LinearBIdeograms")
+            .add("LinearBSyllabary")
+            .add("Lisu")
+            .add("LowSurrogates")
+            .add("Lycian")
+            .add("Lydian")
+            .add("MahjongTiles")
+            .add("Malayalam")
+            .add("Mandaic")
+            .add("MathematicalAlphanumericSymbols")
+            .add("MathematicalOperators")
+            .add("MeeteiMayek")
+            .add("MiscellaneousMathematicalSymbols-A")
+            .add("MiscellaneousMathematicalSymbols-B")
+            .add("MiscellaneousSymbols")
+            .add("MiscellaneousSymbolsandArrows")
+            .add("MiscellaneousSymbolsAndPictographs")
+            .add("MiscellaneousTechnical")
+            .add("ModifierToneLetters")
+            .add("Mongolian")
+            .add("MusicalSymbols")
+            .add("Myanmar")
+            .add("MyanmarExtended-A")
+            .add("NewTaiLue")
+            .add("NKo")
+            .add("NumberForms")
+            .add("Ogham")
+            .add("OlChiki")
+            .add("OldItalic")
+            .add("OldPersian")
+            .add("OldSouthArabian")
+            .add("OldTurkic")
+            .add("OpticalCharacterRecognition")
+            .add("Oriya")
+            .add("Osmanya")
+            .add("Phags-pa")
+            .add("PhaistosDisc")
+            .add("Phoenician")
+            .add("PhoneticExtensions")
+            .add("PhoneticExtensionsSupplement")
+            .add("PlayingCards")
+            .add("PrivateUseArea")
+            .add("Rejang")
+            .add("RumiNumeralSymbols")
+            .add("Runic")
+            .add("Samaritan")
+            .add("Saurashtra")
+            .add("Shavian")
+            .add("Sinhala")
+            .add("SmallFormVariants")
+            .add("SpacingModifierLetters")
+            .add("Specials")
+            .add("Sundanese")
+            .add("SuperscriptsandSubscripts")
+            .add("SupplementalArrows-A")
+            .add("SupplementalArrows-B")
+            .add("SupplementalMathematicalOperators")
+            .add("SupplementalPunctuation")
+            .add("SupplementaryPrivateUseArea-A")
+            .add("SupplementaryPrivateUseArea-B")
+            .add("SylotiNagri")
+            .add("Syriac")
+            .add("Tagalog")
+            .add("Tagbanwa")
+            .add("Tags")
+            .add("TaiLe")
+            .add("TaiTham")
+            .add("TaiViet")
+            .add("TaiXuanJingSymbols")
+            .add("Tamil")
+            .add("Telugu")
+            .add("Thaana")
+            .add("Thai")
+            .add("Tibetan")
+            .add("Tifinagh")
+            .add("TransportAndMapSymbols")
+            .add("Ugaritic")
+            .add("UnifiedCanadianAboriginalSyllabics")
+            .add("UnifiedCanadianAboriginalSyllabicsExtended")
+            .add("Vai")
+            .add("VariationSelectors")
+            .add("VariationSelectorsSupplement")
+            .add("VedicExtensions")
+            .add("VerticalForms")
+            .add("YiRadicals")
+            .add("YiSyllables")
+            .add("YijingHexagramSymbols").build();
+
+    private static final int UNICODE_SCRIPT_FIX_COUNTER = 30;
+
+    private RegexUtils() {
+        throw new UnsupportedOperationException("Utility class should not be instantiated.");
+    }
+
+    /**
+     * Converts XSD regex to Java-compatible regex
+     *
+     * @param xsdRegex XSD regex pattern as it is defined in a YANG source
+     * @return Java-compatible regex
+     */
+    public static String getJavaRegexFromXSD(final String xsdRegex) {
+        return "^" + fixUnicodeScriptPattern(escapeChars(xsdRegex)) + '$';
+    }
+
+    /*
+     * As both '^' and '$' are special anchor characters in java regular
+     * expressions which are implicitly present in XSD regular expressions,
+     * we need to escape them in case they are not defined as part of
+     * character ranges i.e. inside regular square brackets.
+     */
+    private static String escapeChars(final String regex) {
+        final StringBuilder result = new StringBuilder(regex.length());
+        int bracket = 0;
+        boolean escape = false;
+        for (int i = 0; i < regex.length(); i++) {
+            final char ch = regex.charAt(i);
+            switch (ch) {
+                case '[':
+                    if (!escape) {
+                        bracket++;
+                    }
+                    escape = false;
+                    result.append(ch);
+                    break;
+                case ']':
+                    if (!escape) {
+                        bracket--;
+                    }
+                    escape = false;
+                    result.append(ch);
+                    break;
+                case '\\':
+                    escape = !escape;
+                    result.append(ch);
+                    break;
+                case '^':
+                case '$':
+                    if (bracket == 0) {
+                        result.append('\\');
+                    }
+                    escape = false;
+                    result.append(ch);
+                    break;
+                default:
+                    escape = false;
+                    result.append(ch);
+            }
+        }
+        return result.toString();
+    }
+
+    private static String fixUnicodeScriptPattern(String rawPattern) {
+        for (int i = 0; i < UNICODE_SCRIPT_FIX_COUNTER; i++) {
+            try {
+                Pattern.compile(rawPattern);
+                return rawPattern;
+            } catch(final PatternSyntaxException ex) {
+                LOG.debug("Invalid regex pattern syntax in: {}", rawPattern, ex);
+                if (ex.getMessage().contains("Unknown character script name")) {
+                    rawPattern = fixUnknownScripts(ex.getMessage(), rawPattern);
+                } else {
+                    return rawPattern;
+                }
+            }
+        }
+
+        LOG.warn("Regex pattern could not be fixed: {}", rawPattern);
+        return rawPattern;
+    }
+
+    private static String fixUnknownScripts(final String exMessage, final String rawPattern) {
+        StringBuilder result = new StringBuilder(rawPattern);
+        final Matcher matcher = BETWEEN_CURLY_BRACES_PATTERN.matcher(exMessage);
+        if (matcher.find()) {
+            final String capturedGroup = matcher.group(1);
+            if (JAVA_UNICODE_BLOCKS.contains(capturedGroup)) {
+                final int idx = rawPattern.indexOf("Is" + capturedGroup);
+                result = result.replace(idx, idx + 2, "In");
+            }
+        }
+        return result.toString();
+    }
+}
\ No newline at end of file
index d899ed879fbc1d8044f239e9ca45663f92f9443a..c9478c9de63aae4d53ed790be16576f0c901b1a4 100644 (file)
@@ -20,6 +20,7 @@ import org.opendaylight.yangtools.yang.model.api.stmt.ModifierStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.PatternStatement;
 import org.opendaylight.yangtools.yang.model.api.stmt.ReferenceStatement;
 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
+import org.opendaylight.yangtools.yang.model.util.RegexUtils;
 import org.opendaylight.yangtools.yang.parser.spi.SubstatementValidator;
 import org.opendaylight.yangtools.yang.parser.spi.meta.AbstractDeclaredStatement;
 import org.opendaylight.yangtools.yang.parser.spi.meta.AbstractStatementSupport;
@@ -53,7 +54,7 @@ public class PatternStatementImpl extends AbstractDeclaredStatement<PatternConst
 
         @Override
         public PatternConstraint parseArgumentValue(final StmtContext<?, ?, ?> ctx, final String value) {
-            final String pattern = getJavaRegexFromXSD(value);
+            final String pattern = RegexUtils.getJavaRegexFromXSD(value);
 
             try {
                 Pattern.compile(pattern);
@@ -65,57 +66,6 @@ public class PatternStatementImpl extends AbstractDeclaredStatement<PatternConst
             return new PatternConstraintEffectiveImpl(pattern, value, Optional.absent(), Optional.absent());
         }
 
-        static String getJavaRegexFromXSD(final String xsdRegex) {
-            return "^" + Utils.fixUnicodeScriptPattern(escapeChars(xsdRegex)) + '$';
-        }
-
-        /*
-         * As both '^' and '$' are special anchor characters in java regular
-         * expressions which are implicitly present in XSD regular expressions,
-         * we need to escape them in case they are not defined as part of
-         * character ranges i.e. inside regular square brackets.
-         */
-        private static String escapeChars(final String regex) {
-            final StringBuilder result = new StringBuilder(regex.length());
-            int bracket = 0;
-            boolean escape = false;
-            for (int i = 0; i < regex.length(); i++) {
-                final char ch = regex.charAt(i);
-                switch (ch) {
-                case '[':
-                    if (!escape) {
-                        bracket++;
-                    }
-                    escape = false;
-                    result.append(ch);
-                    break;
-                case ']':
-                    if (!escape) {
-                        bracket--;
-                    }
-                    escape = false;
-                    result.append(ch);
-                    break;
-                case '\\':
-                    escape = !escape;
-                    result.append(ch);
-                    break;
-                case '^':
-                case '$':
-                    if (bracket == 0) {
-                        result.append('\\');
-                    }
-                    escape = false;
-                    result.append(ch);
-                    break;
-                default:
-                    escape = false;
-                    result.append(ch);
-                }
-            }
-            return result.toString();
-        }
-
         @Override
         public PatternStatement createDeclared(final StmtContext<PatternConstraint, PatternStatement, ?> ctx) {
             return new PatternStatementImpl(ctx);
index 05993a9c3d31a2ca45dfabf0fd8a4cf0bdef9f81..e80d39d64210079722f7a4cc9501219c4eec8912 100644 (file)
@@ -30,7 +30,6 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
 import javax.annotation.Nullable;
 import javax.annotation.RegEx;
 import javax.xml.xpath.XPath;
@@ -73,7 +72,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public final class Utils {
-    private static final int UNICODE_SCRIPT_FIX_COUNTER = 30;
     private static final Logger LOG = LoggerFactory.getLogger(Utils.class);
     private static final CharMatcher LEFT_PARENTHESIS_MATCHER = CharMatcher.is('(');
     private static final CharMatcher RIGHT_PARENTHESIS_MATCHER = CharMatcher.is(')');
@@ -87,217 +85,6 @@ public final class Utils {
     private static final String YANG_XPATH_FUNCTIONS_STRING =
             "(re-match|deref|derived-from(-or-self)?|enum-value|bit-is-set)(\\()";
     private static final Pattern YANG_XPATH_FUNCTIONS_PATTERN = Pattern.compile(YANG_XPATH_FUNCTIONS_STRING);
-    private static final Pattern BETWEEN_CURLY_BRACES_PATTERN = Pattern.compile("\\{(.+?)\\}");
-    private static final Set<String> JAVA_UNICODE_BLOCKS = ImmutableSet.<String>builder()
-            .add("AegeanNumbers")
-            .add("AlchemicalSymbols")
-            .add("AlphabeticPresentationForms")
-            .add("AncientGreekMusicalNotation")
-            .add("AncientGreekNumbers")
-            .add("AncientSymbols")
-            .add("Arabic")
-            .add("ArabicPresentationForms-A")
-            .add("ArabicPresentationForms-B")
-            .add("ArabicSupplement")
-            .add("Armenian")
-            .add("Arrows")
-            .add("Avestan")
-            .add("Balinese")
-            .add("Bamum")
-            .add("BamumSupplement")
-            .add("BasicLatin")
-            .add("Batak")
-            .add("Bengali")
-            .add("BlockElements")
-            .add("Bopomofo")
-            .add("BopomofoExtended")
-            .add("BoxDrawing")
-            .add("Brahmi")
-            .add("BraillePatterns")
-            .add("Buginese")
-            .add("Buhid")
-            .add("ByzantineMusicalSymbols")
-            .add("Carian")
-            .add("Cham")
-            .add("Cherokee")
-            .add("CJKCompatibility")
-            .add("CJKCompatibilityForms")
-            .add("CJKCompatibilityIdeographs")
-            .add("CJKCompatibilityIdeographsSupplement")
-            .add("CJKRadicalsSupplement")
-            .add("CJKStrokes")
-            .add("CJKSymbolsandPunctuation")
-            .add("CJKUnifiedIdeographs")
-            .add("CJKUnifiedIdeographsExtensionA")
-            .add("CJKUnifiedIdeographsExtensionB")
-            .add("CJKUnifiedIdeographsExtensionC")
-            .add("CJKUnifiedIdeographsExtensionD")
-            .add("CombiningDiacriticalMarks")
-            .add("CombiningDiacriticalMarksSupplement")
-            .add("CombiningHalfMarks")
-            .add("CombiningDiacriticalMarksforSymbols")
-            .add("CommonIndicNumberForms")
-            .add("ControlPictures")
-            .add("Coptic")
-            .add("CountingRodNumerals")
-            .add("Cuneiform")
-            .add("CuneiformNumbersandPunctuation")
-            .add("CurrencySymbols")
-            .add("CypriotSyllabary")
-            .add("Cyrillic")
-            .add("CyrillicExtended-A")
-            .add("CyrillicExtended-B")
-            .add("CyrillicSupplementary")
-            .add("Deseret")
-            .add("Devanagari")
-            .add("DevanagariExtended")
-            .add("Dingbats")
-            .add("DominoTiles")
-            .add("EgyptianHieroglyphs")
-            .add("Emoticons")
-            .add("EnclosedAlphanumericSupplement")
-            .add("EnclosedAlphanumerics")
-            .add("EnclosedCJKLettersandMonths")
-            .add("EnclosedIdeographicSupplement")
-            .add("Ethiopic")
-            .add("EthiopicExtended")
-            .add("EthiopicExtended-A")
-            .add("EthiopicSupplement")
-            .add("GeneralPunctuation")
-            .add("GeometricShapes")
-            .add("Georgian")
-            .add("GeorgianSupplement")
-            .add("Glagolitic")
-            .add("Gothic")
-            .add("GreekandCoptic")
-            .add("GreekExtended")
-            .add("Gujarati")
-            .add("Gurmukhi")
-            .add("HalfwidthandFullwidthForms")
-            .add("HangulCompatibilityJamo")
-            .add("HangulJamo")
-            .add("HangulJamoExtended-A")
-            .add("HangulJamoExtended-B")
-            .add("HangulSyllables")
-            .add("Hanunoo")
-            .add("Hebrew")
-            .add("HighPrivateUseSurrogates")
-            .add("HighSurrogates")
-            .add("Hiragana")
-            .add("IdeographicDescriptionCharacters")
-            .add("ImperialAramaic")
-            .add("InscriptionalPahlavi")
-            .add("InscriptionalParthian")
-            .add("IPAExtensions")
-            .add("Javanese")
-            .add("Kaithi")
-            .add("KanaSupplement")
-            .add("Kanbun")
-            .add("Kangxi Radicals")
-            .add("Kannada")
-            .add("Katakana")
-            .add("KatakanaPhoneticExtensions")
-            .add("KayahLi")
-            .add("Kharoshthi")
-            .add("Khmer")
-            .add("KhmerSymbols")
-            .add("Lao")
-            .add("Latin-1Supplement")
-            .add("LatinExtended-A")
-            .add("LatinExtendedAdditional")
-            .add("LatinExtended-B")
-            .add("LatinExtended-C")
-            .add("LatinExtended-D")
-            .add("Lepcha")
-            .add("LetterlikeSymbols")
-            .add("Limbu")
-            .add("LinearBIdeograms")
-            .add("LinearBSyllabary")
-            .add("Lisu")
-            .add("LowSurrogates")
-            .add("Lycian")
-            .add("Lydian")
-            .add("MahjongTiles")
-            .add("Malayalam")
-            .add("Mandaic")
-            .add("MathematicalAlphanumericSymbols")
-            .add("MathematicalOperators")
-            .add("MeeteiMayek")
-            .add("MiscellaneousMathematicalSymbols-A")
-            .add("MiscellaneousMathematicalSymbols-B")
-            .add("MiscellaneousSymbols")
-            .add("MiscellaneousSymbolsandArrows")
-            .add("MiscellaneousSymbolsAndPictographs")
-            .add("MiscellaneousTechnical")
-            .add("ModifierToneLetters")
-            .add("Mongolian")
-            .add("MusicalSymbols")
-            .add("Myanmar")
-            .add("MyanmarExtended-A")
-            .add("NewTaiLue")
-            .add("NKo")
-            .add("NumberForms")
-            .add("Ogham")
-            .add("OlChiki")
-            .add("OldItalic")
-            .add("OldPersian")
-            .add("OldSouthArabian")
-            .add("OldTurkic")
-            .add("OpticalCharacterRecognition")
-            .add("Oriya")
-            .add("Osmanya")
-            .add("Phags-pa")
-            .add("PhaistosDisc")
-            .add("Phoenician")
-            .add("PhoneticExtensions")
-            .add("PhoneticExtensionsSupplement")
-            .add("PlayingCards")
-            .add("PrivateUseArea")
-            .add("Rejang")
-            .add("RumiNumeralSymbols")
-            .add("Runic")
-            .add("Samaritan")
-            .add("Saurashtra")
-            .add("Shavian")
-            .add("Sinhala")
-            .add("SmallFormVariants")
-            .add("SpacingModifierLetters")
-            .add("Specials")
-            .add("Sundanese")
-            .add("SuperscriptsandSubscripts")
-            .add("SupplementalArrows-A")
-            .add("SupplementalArrows-B")
-            .add("SupplementalMathematicalOperators")
-            .add("SupplementalPunctuation")
-            .add("SupplementaryPrivateUseArea-A")
-            .add("SupplementaryPrivateUseArea-B")
-            .add("SylotiNagri")
-            .add("Syriac")
-            .add("Tagalog")
-            .add("Tagbanwa")
-            .add("Tags")
-            .add("TaiLe")
-            .add("TaiTham")
-            .add("TaiViet")
-            .add("TaiXuanJingSymbols")
-            .add("Tamil")
-            .add("Telugu")
-            .add("Thaana")
-            .add("Thai")
-            .add("Tibetan")
-            .add("Tifinagh")
-            .add("TransportAndMapSymbols")
-            .add("Ugaritic")
-            .add("UnifiedCanadianAboriginalSyllabics")
-            .add("UnifiedCanadianAboriginalSyllabicsExtended")
-            .add("Vai")
-            .add("VariationSelectors")
-            .add("VariationSelectorsSupplement")
-            .add("VedicExtensions")
-            .add("VerticalForms")
-            .add("YiRadicals")
-            .add("YiSyllables")
-            .add("YijingHexagramSymbols").build();
 
     private static final Map<String, DeviateKind> KEYWORD_TO_DEVIATE_MAP;
     static {
@@ -664,38 +451,6 @@ public final class Utils {
         return string;
     }
 
-    public static String fixUnicodeScriptPattern(String rawPattern) {
-        for (int i = 0; i < UNICODE_SCRIPT_FIX_COUNTER; i++) {
-            try {
-                Pattern.compile(rawPattern);
-                return rawPattern;
-            } catch(final PatternSyntaxException ex) {
-                LOG.debug("Invalid regex pattern syntax in: {}", rawPattern, ex);
-                if (ex.getMessage().contains("Unknown character script name")) {
-                    rawPattern = fixUnknownScripts(ex.getMessage(), rawPattern);
-                } else {
-                    return rawPattern;
-                }
-            }
-        }
-
-        LOG.warn("Regex pattern could not be fixed: {}", rawPattern);
-        return rawPattern;
-    }
-
-    private static String fixUnknownScripts(final String exMessage, final String rawPattern) {
-        StringBuilder result = new StringBuilder(rawPattern);
-        final Matcher matcher = BETWEEN_CURLY_BRACES_PATTERN.matcher(exMessage);
-        if (matcher.find()) {
-            final String capturedGroup = matcher.group(1);
-            if (JAVA_UNICODE_BLOCKS.contains(capturedGroup)) {
-                final int idx = rawPattern.indexOf("Is" + capturedGroup);
-                result = result.replace(idx, idx + 2, "In");
-            }
-        }
-        return result.toString();
-    }
-
     public static boolean belongsToTheSameModule(final QName targetStmtQName, final QName sourceStmtQName) {
         if (targetStmtQName.getModule().equals(sourceStmtQName.getModule())) {
             return true;
index 525302b9b869eb4e33a9996532b0295343f049a2..62654c814e604d7e6d68d621b70e825772150cc6 100644 (file)
@@ -25,6 +25,7 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
+import org.opendaylight.yangtools.yang.model.util.RegexUtils;
 import org.opendaylight.yangtools.yang.stmt.StmtTestUtils;
 
 public class Bug5410Test {
@@ -199,7 +200,7 @@ public class Bug5410Test {
     }
 
     private static String javaRegexFromXSD(final String xsdRegex) {
-        return PatternStatementImpl.Definition.getJavaRegexFromXSD(xsdRegex);
+        return RegexUtils.getJavaRegexFromXSD(xsdRegex);
     }
 
     private static boolean testMatch(final String javaRegex, final String value) {
index 1669b1a477649b62f759a2a4c27bc2c499263b96..969f8d633e535fe495059b28d6883c50d224c5f0 100644 (file)
@@ -15,119 +15,119 @@ import java.lang.reflect.InvocationTargetException;
 import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
 import org.junit.Test;
-import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.Utils;
+import org.opendaylight.yangtools.yang.model.util.RegexUtils;
 
 public class Bug4079Test {
 
     @Test
     public void testValidPatternFix() {
-        String fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\p{IsArrows})*+");
-        assertEquals("(\\p{InArrows})*+", fixedUnicodeScriptPattern);
+        String fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\p{IsArrows})*+");
+        assertEquals("^(\\p{InArrows})*+$", fixedUnicodeScriptPattern);
         assertNotNull(Pattern.compile(fixedUnicodeScriptPattern));
 
-        fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\p{IsDingbats})++");
-        assertEquals("(\\p{InDingbats})++", fixedUnicodeScriptPattern);
+        fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\p{IsDingbats})++");
+        assertEquals("^(\\p{InDingbats})++$", fixedUnicodeScriptPattern);
         assertNotNull(Pattern.compile(fixedUnicodeScriptPattern));
 
-        fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\p{IsSpecials})?+");
-        assertEquals("(\\p{InSpecials})?+", fixedUnicodeScriptPattern);
+        fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\p{IsSpecials})?+");
+        assertEquals("^(\\p{InSpecials})?+$", fixedUnicodeScriptPattern);
         assertNotNull(Pattern.compile(fixedUnicodeScriptPattern));
 
-        fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\p{IsBatak}){4}+");
-        assertEquals("(\\p{IsBatak}){4}+", fixedUnicodeScriptPattern);
+        fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\p{IsBatak}){4}+");
+        assertEquals("^(\\p{IsBatak}){4}+$", fixedUnicodeScriptPattern);
         assertNotNull(Pattern.compile(fixedUnicodeScriptPattern));
 
-        fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\p{IsLatin}){4,6}+");
-        assertEquals("(\\p{IsLatin}){4,6}+", fixedUnicodeScriptPattern);
+        fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\p{IsLatin}){4,6}+");
+        assertEquals("^(\\p{IsLatin}){4,6}+$", fixedUnicodeScriptPattern);
         assertNotNull(Pattern.compile(fixedUnicodeScriptPattern));
 
-        fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\p{IsTibetan}){4,}+");
-        assertEquals("(\\p{IsTibetan}){4,}+", fixedUnicodeScriptPattern);
+        fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\p{IsTibetan}){4,}+");
+        assertEquals("^(\\p{IsTibetan}){4,}+$", fixedUnicodeScriptPattern);
         assertNotNull(Pattern.compile(fixedUnicodeScriptPattern));
 
-        fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\p{IsAlphabetic}){4}?");
-        assertEquals("(\\p{IsAlphabetic}){4}?", fixedUnicodeScriptPattern);
+        fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\p{IsAlphabetic}){4}?");
+        assertEquals("^(\\p{IsAlphabetic}){4}?$", fixedUnicodeScriptPattern);
         assertNotNull(Pattern.compile(fixedUnicodeScriptPattern));
 
-        fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\p{IsLowercase}){4,6}?");
-        assertEquals("(\\p{IsLowercase}){4,6}?", fixedUnicodeScriptPattern);
+        fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\p{IsLowercase}){4,6}?");
+        assertEquals("^(\\p{IsLowercase}){4,6}?$", fixedUnicodeScriptPattern);
         assertNotNull(Pattern.compile(fixedUnicodeScriptPattern));
 
-        fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\p{IsUppercase}){4,}?");
-        assertEquals("(\\p{IsUppercase}){4,}?", fixedUnicodeScriptPattern);
+        fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\p{IsUppercase}){4,}?");
+        assertEquals("^(\\p{IsUppercase}){4,}?$", fixedUnicodeScriptPattern);
         assertNotNull(Pattern.compile(fixedUnicodeScriptPattern));
 
-        fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\p{IsBasicLatin}|\\p{IsLatin-1Supplement})*");
-        assertEquals("(\\p{InBasicLatin}|\\p{InLatin-1Supplement})*", fixedUnicodeScriptPattern);
+        fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\p{IsBasicLatin}|\\p{IsLatin-1Supplement})*");
+        assertEquals("^(\\p{InBasicLatin}|\\p{InLatin-1Supplement})*$", fixedUnicodeScriptPattern);
         assertNotNull(Pattern.compile(fixedUnicodeScriptPattern));
 
-        fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\p{InBasicLatin}|\\p{InLatin-1Supplement})+");
-        assertEquals("(\\p{InBasicLatin}|\\p{InLatin-1Supplement})+", fixedUnicodeScriptPattern);
+        fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\p{InBasicLatin}|\\p{InLatin-1Supplement})+");
+        assertEquals("^(\\p{InBasicLatin}|\\p{InLatin-1Supplement})+$", fixedUnicodeScriptPattern);
         assertNotNull(Pattern.compile(fixedUnicodeScriptPattern));
 
-        fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\p{IsBasicLatin}|\\p{InLatin-1Supplement})?");
-        assertEquals("(\\p{InBasicLatin}|\\p{InLatin-1Supplement})?", fixedUnicodeScriptPattern);
+        fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\p{IsBasicLatin}|\\p{InLatin-1Supplement})?");
+        assertEquals("^(\\p{InBasicLatin}|\\p{InLatin-1Supplement})?$", fixedUnicodeScriptPattern);
         assertNotNull(Pattern.compile(fixedUnicodeScriptPattern));
 
-        fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\p{InBasicLatin}|\\p{IsLatin-1Supplement}){4}");
-        assertEquals("(\\p{InBasicLatin}|\\p{InLatin-1Supplement}){4}", fixedUnicodeScriptPattern);
+        fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\p{InBasicLatin}|\\p{IsLatin-1Supplement}){4}");
+        assertEquals("^(\\p{InBasicLatin}|\\p{InLatin-1Supplement}){4}$", fixedUnicodeScriptPattern);
         assertNotNull(Pattern.compile(fixedUnicodeScriptPattern));
 
-        fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\p{IsLatin}|\\p{IsArmenian}){2,4}");
-        assertEquals("(\\p{IsLatin}|\\p{IsArmenian}){2,4}", fixedUnicodeScriptPattern);
+        fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\p{IsLatin}|\\p{IsArmenian}){2,4}");
+        assertEquals("^(\\p{IsLatin}|\\p{IsArmenian}){2,4}$", fixedUnicodeScriptPattern);
         assertNotNull(Pattern.compile(fixedUnicodeScriptPattern));
 
-        fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\p{IsLatin}|\\p{IsBasicLatin}){2,}");
-        assertEquals("(\\p{IsLatin}|\\p{InBasicLatin}){2,}", fixedUnicodeScriptPattern);
+        fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\p{IsLatin}|\\p{IsBasicLatin}){2,}");
+        assertEquals("^(\\p{IsLatin}|\\p{InBasicLatin}){2,}$", fixedUnicodeScriptPattern);
         assertNotNull(Pattern.compile(fixedUnicodeScriptPattern));
 
-        fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\p{IsBasicLatin}|\\p{IsLatin})*?");
-        assertEquals("(\\p{InBasicLatin}|\\p{IsLatin})*?", fixedUnicodeScriptPattern);
+        fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\p{IsBasicLatin}|\\p{IsLatin})*?");
+        assertEquals("^(\\p{InBasicLatin}|\\p{IsLatin})*?$", fixedUnicodeScriptPattern);
         assertNotNull(Pattern.compile(fixedUnicodeScriptPattern));
 
-        fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern(
+        fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD(
                 "(\\p{IsBasicLatin}|\\p{IsLatin-1Supplement}|\\p{IsArrows})+?");
-        assertEquals("(\\p{InBasicLatin}|\\p{InLatin-1Supplement}|\\p{InArrows})+?", fixedUnicodeScriptPattern);
+        assertEquals("^(\\p{InBasicLatin}|\\p{InLatin-1Supplement}|\\p{InArrows})+?$", fixedUnicodeScriptPattern);
         assertNotNull(Pattern.compile(fixedUnicodeScriptPattern));
 
-        fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern(
+        fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD(
                 "(\\p{InBasicLatin}|\\p{IsLatin-1Supplement}|\\p{IsLatin})??");
-        assertEquals("(\\p{InBasicLatin}|\\p{InLatin-1Supplement}|\\p{IsLatin})??", fixedUnicodeScriptPattern);
+        assertEquals("^(\\p{InBasicLatin}|\\p{InLatin-1Supplement}|\\p{IsLatin})??$", fixedUnicodeScriptPattern);
         assertNotNull(Pattern.compile(fixedUnicodeScriptPattern));
 
-        fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\\\\\p{IsBasicLatin})*+");
-        assertEquals("(\\\\\\p{InBasicLatin})*+", fixedUnicodeScriptPattern);
+        fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\\\\\p{IsBasicLatin})*+");
+        assertEquals("^(\\\\\\p{InBasicLatin})*+$", fixedUnicodeScriptPattern);
         assertNotNull(Pattern.compile(fixedUnicodeScriptPattern));
 
-        fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\\\\\\\\\p{IsBasicLatin})*+");
-        assertEquals("(\\\\\\\\\\p{InBasicLatin})*+", fixedUnicodeScriptPattern);
+        fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\\\\\\\\\p{IsBasicLatin})*+");
+        assertEquals("^(\\\\\\\\\\p{InBasicLatin})*+$", fixedUnicodeScriptPattern);
         assertNotNull(Pattern.compile(fixedUnicodeScriptPattern));
 
-        fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\\\\\\\\\\\\\p{IsBasicLatin})*+");
-        assertEquals("(\\\\\\\\\\\\\\p{InBasicLatin})*+", fixedUnicodeScriptPattern);
+        fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\\\\\\\\\\\\\p{IsBasicLatin})*+");
+        assertEquals("^(\\\\\\\\\\\\\\p{InBasicLatin})*+$", fixedUnicodeScriptPattern);
         assertNotNull(Pattern.compile(fixedUnicodeScriptPattern));
     }
 
     @Test(expected = PatternSyntaxException.class)
     public void testInvalidPattern() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
-        String fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\\\p{IsBasicLatin})*+");
-        assertEquals("(\\\\p{IsBasicLatin})*+", fixedUnicodeScriptPattern);
+        String fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\\\p{IsBasicLatin})*+");
+        assertEquals("^(\\\\p{IsBasicLatin})*+$", fixedUnicodeScriptPattern);
         // should throw exception
         Pattern.compile(fixedUnicodeScriptPattern);
     }
 
     @Test(expected = PatternSyntaxException.class)
     public void testInvalidPattern2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
-        String fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\p{IsSpecials}|\\\\\\\\p{IsBasicLatin})*+");
-        assertEquals("(\\p{InSpecials}|\\\\\\\\p{IsBasicLatin})*+", fixedUnicodeScriptPattern);
+        String fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\p{IsSpecials}|\\\\\\\\p{IsBasicLatin})*+");
+        assertEquals("^(\\p{InSpecials}|\\\\\\\\p{IsBasicLatin})*+$", fixedUnicodeScriptPattern);
         // should throw exception
         Pattern.compile(fixedUnicodeScriptPattern);
     }
 
     @Test(expected = PatternSyntaxException.class)
     public void testInvalidPattern3() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
-        String fixedUnicodeScriptPattern = Utils.fixUnicodeScriptPattern("(\\\\\\\\\\\\p{IsBasicLatin}|\\p{IsTags})*+");
-        assertEquals("(\\\\\\\\\\\\p{IsBasicLatin}|\\p{IsTags})*+", fixedUnicodeScriptPattern);
+        String fixedUnicodeScriptPattern = RegexUtils.getJavaRegexFromXSD("(\\\\\\\\\\\\p{IsBasicLatin}|\\p{IsTags})*+");
+        assertEquals("^(\\\\\\\\\\\\p{IsBasicLatin}|\\p{IsTags})*+$", fixedUnicodeScriptPattern);
         // should throw exception
         Pattern.compile(fixedUnicodeScriptPattern);
     }