From: Igor Foltin Date: Fri, 7 Apr 2017 15:17:23 +0000 (+0200) Subject: Bug 7847: Implement YANG 1.1 XPath functions in YangFunctionContext X-Git-Tag: release/carbon~14 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=cf460e8ee03cbe573a8ca2604280f090ec41f824;p=yangtools.git Bug 7847: Implement YANG 1.1 XPath functions in YangFunctionContext 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 Signed-off-by: Robert Varga --- diff --git a/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/JaxenDocumentContext.java b/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/JaxenDocument.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 0b0e26ebb3..a929061c34 100644 --- a/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/JaxenDocumentContext.java +++ b/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/JaxenDocument.java @@ -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; + } } diff --git a/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/JaxenSchemaContext.java b/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/JaxenSchemaContext.java index 91db1a25b0..b781381799 100644 --- a/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/JaxenSchemaContext.java +++ b/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/JaxenSchemaContext.java @@ -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 index 0000000000..1d839823d6 --- /dev/null +++ b/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/LeafrefXPathStringParsingPathArgumentBuilder.java @@ -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> { + + 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 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 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 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 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> 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 diff --git a/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/NormalizedNodeContextSupport.java b/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/NormalizedNodeContextSupport.java index cff335e288..efba94a64c 100644 --- a/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/NormalizedNodeContextSupport.java +++ b/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/NormalizedNodeContextSupport.java @@ -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(); diff --git a/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/NormalizedNodeNavigator.java b/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/NormalizedNodeNavigator.java index 0954ecfb92..78e61844d5 100644 --- a/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/NormalizedNodeNavigator.java +++ b/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/NormalizedNodeNavigator.java @@ -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 { private NormalizedNodeContext next; diff --git a/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/YangFunctionContext.java b/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/YangFunctionContext.java index 87c8976740..a335a5099f 100644 --- a/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/YangFunctionContext.java +++ b/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/YangFunctionContext.java @@ -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 pathArguments = path.getPathArguments(); + if (pathArguments.get(0).getNodeType().equals(rootNode.getNodeType())) { + final List relPath = pathArguments.subList(1, pathArguments.size()); + final Optional> 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 pathArguments = builder.build(); + final NormalizedNodeNavigator navigator = (NormalizedNodeNavigator) currentNodeContext.getNavigator(); + final NormalizedNode rootNode = navigator.getRootNode(); + if (pathArguments.get(0).getNodeType().equals(rootNode.getNodeType())) { + final List relPath = pathArguments.subList(1, pathArguments.size()); + final Optional> 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 pathArguments = builder.build(); + final NormalizedNode relativeNode = relativeNodeContext.getNode(); + final Optional> 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 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 ancestorIdentities = new HashSet<>(); + collectAncestorIdentities(currentNodeIdentitySchemaNode, ancestorIdentities); + + return Boolean.valueOf(ancestorIdentities.contains(identityArgSchemaNode)); + }; + + private static void collectAncestorIdentities(final IdentitySchemaNode identity, + final Set 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 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 ancestorOrSelfAxisIterator; + try { + ancestorOrSelfAxisIterator = currentNodeContext.getContextSupport().getNavigator() + .getAncestorOrSelfAxisIterator(currentNodeContext); + } catch (UnsupportedAxisException ex) { + throw new JaxenRuntimeException(ex); + } + + final Deque 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 index 0000000000..a79ab0805d --- /dev/null +++ b/yang/yang-data-jaxen/src/test/java/org/opendaylight/yangtools/yang/data/jaxen/YangXPathFunctionsTest.java @@ -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 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 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 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 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 converterBiMap = HashBiMap.create(); + converterBiMap.put("bar-prefix", barModule); + + final NormalizedNodeContextSupport normalizedNodeContextSupport = NormalizedNodeContextSupport.create( + (JaxenDocument) jaxenDocument, Maps.asConverter(converterBiMap)); + + final ImmutableMap.Builder builder = ImmutableMap.builder(); + final ImmutableMap 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 converterBiMap = HashBiMap.create(); + converterBiMap.put("foo-prefix", fooModule); + + final NormalizedNodeContextSupport normalizedNodeContextSupport = NormalizedNodeContextSupport.create( + (JaxenDocument) jaxenDocument, Maps.asConverter(converterBiMap)); + + final ImmutableMap.Builder builder = ImmutableMap.builder(); + final ImmutableMap 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 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 converterBiMap = HashBiMap.create(); + converterBiMap.put("foo-prefix", fooModule); + + final NormalizedNodeContextSupport normalizedNodeContextSupport = NormalizedNodeContextSupport.create( + (JaxenDocument) jaxenDocument, Maps.asConverter(converterBiMap)); + + final ImmutableMap.Builder builder = ImmutableMap.builder(); + final ImmutableMap 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 index 0000000000..3d1886123d --- /dev/null +++ b/yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/bit-is-set-function/foo.yang @@ -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 index 0000000000..7dfda620c0 --- /dev/null +++ b/yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/deref-function-iid/foo.yang @@ -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 index 0000000000..a5c771af0d --- /dev/null +++ b/yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/deref-function-leafref/foo.yang @@ -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 index 0000000000..1532fc78c4 --- /dev/null +++ b/yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/derived-from-function/bar.yang @@ -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 index 0000000000..7fec0c6110 --- /dev/null +++ b/yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/derived-from-function/foo.yang @@ -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 index 0000000000..40dc2fd11b --- /dev/null +++ b/yang/yang-data-jaxen/src/test/resources/yang-xpath-functions-test/enum-value-function/foo.yang @@ -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 index 0000000000..862d7e5459 --- /dev/null +++ b/yang/yang-model-util/src/main/java/org/opendaylight/yangtools/yang/model/util/RegexUtils.java @@ -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 JAVA_UNICODE_BLOCKS = ImmutableSet.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 diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/PatternStatementImpl.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/PatternStatementImpl.java index d899ed879f..c9478c9de6 100644 --- a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/PatternStatementImpl.java +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/PatternStatementImpl.java @@ -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 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 ctx) { return new PatternStatementImpl(ctx); diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/Utils.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/Utils.java index 05993a9c3d..e80d39d642 100644 --- a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/Utils.java +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/Utils.java @@ -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 JAVA_UNICODE_BLOCKS = ImmutableSet.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 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; diff --git a/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/Bug5410Test.java b/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/Bug5410Test.java index 525302b9b8..62654c814e 100644 --- a/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/Bug5410Test.java +++ b/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/parser/stmt/rfc6020/Bug5410Test.java @@ -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) { diff --git a/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/stmt/Bug4079Test.java b/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/stmt/Bug4079Test.java index 1669b1a477..969f8d633e 100644 --- a/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/stmt/Bug4079Test.java +++ b/yang/yang-parser-impl/src/test/java/org/opendaylight/yangtools/yang/stmt/Bug4079Test.java @@ -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); }