2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.yangtools.yang.data.jaxen;
10 import com.google.common.base.Preconditions;
11 import com.google.common.base.Splitter;
12 import com.google.common.base.Verify;
13 import java.util.AbstractMap.SimpleImmutableEntry;
14 import java.util.List;
15 import java.util.Map.Entry;
16 import java.util.Optional;
18 import org.eclipse.jdt.annotation.Nullable;
19 import org.jaxen.Context;
20 import org.jaxen.ContextSupport;
21 import org.jaxen.Function;
22 import org.jaxen.FunctionCallException;
23 import org.jaxen.FunctionContext;
24 import org.jaxen.UnresolvableException;
25 import org.jaxen.XPathFunctionContext;
26 import org.opendaylight.yangtools.yang.common.QName;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
29 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
34 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.Module;
37 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
38 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
39 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
40 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
41 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
43 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
44 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
45 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
46 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
47 import org.opendaylight.yangtools.yang.model.util.RegexUtils;
50 * A {@link FunctionContext} which contains also YANG-specific functions current(), re-match(), deref(),
51 * derived-from(), derived-from-or-self(), enum-value() and bit-is-set().
53 final class YangFunctionContext implements FunctionContext {
54 private static final Splitter COLON_SPLITTER = Splitter.on(':');
55 private static final Double DOUBLE_NAN = Double.NaN;
57 // Core XPath functions, as per http://tools.ietf.org/html/rfc6020#section-6.4.1
58 private static final FunctionContext XPATH_FUNCTION_CONTEXT = new XPathFunctionContext(false);
60 // Singleton instance of reuse
61 private static final YangFunctionContext INSTANCE = new YangFunctionContext();
63 private YangFunctionContext() {
66 static YangFunctionContext getInstance() {
71 public Function getFunction(final String namespaceURI, final String prefix, final String localName)
72 throws UnresolvableException {
76 return YangFunctionContext::bitIsSet;
78 return YangFunctionContext::current;
80 return YangFunctionContext::deref;
82 return YangFunctionContext::derivedFrom;
83 case "derived-from-or-self":
84 return YangFunctionContext::derivedFromOrSelf;
86 return YangFunctionContext::enumValueFunction;
88 return YangFunctionContext::reMatch;
94 return XPATH_FUNCTION_CONTEXT.getFunction(namespaceURI, prefix, localName);
97 // bit-is-set(node-set nodes, string bit-name) function as per
98 // https://tools.ietf.org/html/rfc7950#section-10.6.1
99 private static boolean bitIsSet(final Context context, final List<?> args) throws FunctionCallException {
100 if (args == null || args.size() != 1) {
101 throw new FunctionCallException("bit-is-set() takes two arguments: node-set nodes, string bit-name");
104 if (!(args.get(0) instanceof String)) {
105 throw new FunctionCallException("Argument bit-name of bit-is-set() function should be a String");
108 final String bitName = (String) args.get(0);
110 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
112 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
113 final TypedDataSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(currentNodeContext);
115 final TypeDefinition<?> nodeType = correspondingSchemaNode.getType();
116 if (!(nodeType instanceof BitsTypeDefinition)) {
120 final Object nodeValue = currentNodeContext.getNode().getValue();
121 if (!(nodeValue instanceof Set)) {
125 final BitsTypeDefinition bitsType = (BitsTypeDefinition) nodeType;
126 Preconditions.checkState(containsBit(bitsType, bitName), "Bit %s does not belong to bits %s.", bitName,
128 return ((Set<?>)nodeValue).contains(bitName);
131 // current() function, as per http://tools.ietf.org/html/rfc6020#section-6.4.1
132 private static NormalizedNodeContext current(final Context context, final List<?> args)
133 throws FunctionCallException {
134 if (!args.isEmpty()) {
135 throw new FunctionCallException("current() takes no arguments.");
138 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
139 return (NormalizedNodeContext) context;
142 // deref(node-set nodes) function as per https://tools.ietf.org/html/rfc7950#section-10.3.1
143 private static NormalizedNode<?, ?> deref(final Context context, final List<?> args) throws FunctionCallException {
144 if (!args.isEmpty()) {
145 throw new FunctionCallException("deref() takes only one argument: node-set nodes.");
148 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
149 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
150 final TypedDataSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(currentNodeContext);
152 final Object nodeValue = currentNodeContext.getNode().getValue();
153 final TypeDefinition<?> type = correspondingSchemaNode.getType();
154 if (type instanceof InstanceIdentifierTypeDefinition) {
155 return nodeValue instanceof YangInstanceIdentifier
156 ? getNodeReferencedByInstanceIdentifier((YangInstanceIdentifier) nodeValue, currentNodeContext)
159 if (type instanceof LeafrefTypeDefinition) {
160 final RevisionAwareXPath xpath = ((LeafrefTypeDefinition) type).getPathStatement();
161 return getNodeReferencedByLeafref(xpath, currentNodeContext, getSchemaContext(currentNodeContext),
162 correspondingSchemaNode, nodeValue);
167 // derived-from(node-set nodes, string identity) function as per https://tools.ietf.org/html/rfc7950#section-10.4.1
168 private static boolean derivedFrom(final Context context, final List<?> args) throws FunctionCallException {
169 final Entry<IdentitySchemaNode, IdentitySchemaNode> ids = commonDerivedFrom("derived-from", context, args);
170 return ids != null && isAncestorOf(ids.getKey(), ids.getValue());
173 // derived-from-or-self(node-set nodes, string identity) function as per
174 // https://tools.ietf.org/html/rfc7950#section-10.4.2
175 private static boolean derivedFromOrSelf(final Context context, final List<?> args) throws FunctionCallException {
176 final Entry<IdentitySchemaNode, IdentitySchemaNode> ids = commonDerivedFrom("derived-from-or-self", context,
178 return ids != null && (ids.getValue().equals(ids.getKey()) || isAncestorOf(ids.getKey(), ids.getValue()));
181 private static @Nullable Entry<IdentitySchemaNode, IdentitySchemaNode> commonDerivedFrom(final String functionName,
182 final Context context, final List<?> args) throws FunctionCallException {
183 if (args == null || args.size() != 1) {
184 throw new FunctionCallException(functionName + "() takes two arguments: node-set nodes, string identity");
186 if (!(args.get(0) instanceof String)) {
187 throw new FunctionCallException("Argument 'identity' of " + functionName
188 + "() function should be a String.");
190 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
192 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
193 final TypedDataSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(currentNodeContext);
195 final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
196 return correspondingSchemaNode.getType() instanceof IdentityrefTypeDefinition
197 && currentNodeContext.getNode().getValue() instanceof QName ? new SimpleImmutableEntry<>(
198 getIdentitySchemaNodeFromString((String) args.get(0), schemaContext, correspondingSchemaNode),
199 getIdentitySchemaNodeFromQName((QName) currentNodeContext.getNode().getValue(), schemaContext))
203 // enum-value(node-set nodes) function as per https://tools.ietf.org/html/rfc7950#section-10.5.1
204 private static Object enumValueFunction(final Context context, final List<?> args) throws FunctionCallException {
205 if (!args.isEmpty()) {
206 throw new FunctionCallException("enum-value() takes one argument: node-set nodes.");
209 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
211 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
212 final TypedDataSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(currentNodeContext);
214 final TypeDefinition<?> nodeType = correspondingSchemaNode.getType();
215 if (!(nodeType instanceof EnumTypeDefinition)) {
219 final Object nodeValue = currentNodeContext.getNode().getValue();
220 if (!(nodeValue instanceof String)) {
224 final EnumTypeDefinition enumerationType = (EnumTypeDefinition) nodeType;
225 final String enumName = (String) nodeValue;
227 return getEnumValue(enumerationType, enumName);
230 // re-match(string subject, string pattern) function as per https://tools.ietf.org/html/rfc7950#section-10.2.1
231 private static boolean reMatch(final Context context, final List<?> args) throws FunctionCallException {
232 if (args == null || args.size() != 2) {
233 throw new FunctionCallException("re-match() takes two arguments: string subject, string pattern.");
235 final Object subject = args.get(0);
236 if (!(subject instanceof String)) {
237 throw new FunctionCallException("First argument of re-match() should be a String.");
239 final Object pattern = args.get(1);
240 if (!(pattern instanceof String)) {
241 throw new FunctionCallException("Second argument of re-match() should be a String.");
244 return ((String) subject).matches(RegexUtils.getJavaRegexFromXSD((String) pattern));
247 private static boolean isAncestorOf(final IdentitySchemaNode identity, final IdentitySchemaNode descendant) {
248 for (IdentitySchemaNode base : descendant.getBaseIdentities()) {
249 if (identity.equals(base) || isAncestorOf(identity, base)) {
256 private static IdentitySchemaNode getIdentitySchemaNodeFromQName(final QName identityQName,
257 final SchemaContext schemaContext) {
258 final Optional<Module> module = schemaContext.findModule(identityQName.getModule());
259 Preconditions.checkArgument(module.isPresent(), "Module for %s not found", identityQName);
260 return findIdentitySchemaNodeInModule(module.get(), identityQName);
263 private static IdentitySchemaNode getIdentitySchemaNodeFromString(final String identity,
264 final SchemaContext schemaContext, final TypedDataSchemaNode correspondingSchemaNode) {
265 final List<String> identityPrefixAndName = COLON_SPLITTER.splitToList(identity);
266 final Module module = schemaContext.findModule(correspondingSchemaNode.getQName().getModule()).get();
267 if (identityPrefixAndName.size() == 2) {
268 // prefix of local module
269 if (identityPrefixAndName.get(0).equals(module.getPrefix())) {
270 return findIdentitySchemaNodeInModule(module, QName.create(module.getQNameModule(),
271 identityPrefixAndName.get(1)));
274 // prefix of imported module
275 for (final ModuleImport moduleImport : module.getImports()) {
276 if (identityPrefixAndName.get(0).equals(moduleImport.getPrefix())) {
277 final Module importedModule = schemaContext.findModule(moduleImport.getModuleName(),
278 moduleImport.getRevision()).get();
279 return findIdentitySchemaNodeInModule(importedModule, QName.create(
280 importedModule.getQNameModule(), identityPrefixAndName.get(1)));
284 throw new IllegalArgumentException(String.format("Cannot resolve prefix '%s' from identity '%s'.",
285 identityPrefixAndName.get(0), identity));
288 if (identityPrefixAndName.size() == 1) {
290 return findIdentitySchemaNodeInModule(module, QName.create(module.getQNameModule(),
291 identityPrefixAndName.get(0)));
294 throw new IllegalArgumentException(String.format("Malformed identity argument: %s.", identity));
297 private static IdentitySchemaNode findIdentitySchemaNodeInModule(final Module module, final QName identityQName) {
298 for (final IdentitySchemaNode id : module.getIdentities()) {
299 if (identityQName.equals(id.getQName())) {
304 throw new IllegalArgumentException(String.format("Identity %s does not have a corresponding"
305 + " identity schema node in the module %s.", identityQName, module));
308 private static NormalizedNode<?, ?> getNodeReferencedByInstanceIdentifier(final YangInstanceIdentifier path,
309 final NormalizedNodeContext currentNodeContext) {
310 final NormalizedNodeNavigator navigator = (NormalizedNodeNavigator) currentNodeContext.getNavigator();
311 final NormalizedNode<?, ?> rootNode = navigator.getDocument().getRootNode();
312 final List<PathArgument> pathArguments = path.getPathArguments();
313 if (pathArguments.get(0).getNodeType().equals(rootNode.getNodeType())) {
314 final List<PathArgument> relPath = pathArguments.subList(1, pathArguments.size());
315 final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(rootNode, relPath);
316 if (possibleNode.isPresent()) {
317 return possibleNode.get();
324 private static NormalizedNode<?, ?> getNodeReferencedByLeafref(final RevisionAwareXPath xpath,
325 final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext,
326 final TypedDataSchemaNode correspondingSchemaNode, final Object nodeValue) {
327 final NormalizedNode<?, ?> referencedNode = xpath.isAbsolute() ? getNodeReferencedByAbsoluteLeafref(xpath,
328 currentNodeContext, schemaContext, correspondingSchemaNode) : getNodeReferencedByRelativeLeafref(xpath,
329 currentNodeContext, schemaContext, correspondingSchemaNode);
331 if (referencedNode instanceof LeafSetNode) {
332 return getReferencedLeafSetEntryNode((LeafSetNode<?>) referencedNode, nodeValue);
335 if (referencedNode instanceof LeafNode && referencedNode.getValue().equals(nodeValue)) {
336 return referencedNode;
342 private static NormalizedNode<?, ?> getNodeReferencedByAbsoluteLeafref(final RevisionAwareXPath xpath,
343 final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext,
344 final TypedDataSchemaNode correspondingSchemaNode) {
345 final LeafrefXPathStringParsingPathArgumentBuilder builder = new LeafrefXPathStringParsingPathArgumentBuilder(
346 xpath.toString(), schemaContext, correspondingSchemaNode, currentNodeContext);
347 final List<PathArgument> pathArguments = builder.build();
348 final NormalizedNodeNavigator navigator = (NormalizedNodeNavigator) currentNodeContext.getNavigator();
349 final NormalizedNode<?, ?> rootNode = navigator.getDocument().getRootNode();
350 if (pathArguments.get(0).getNodeType().equals(rootNode.getNodeType())) {
351 final List<PathArgument> relPath = pathArguments.subList(1, pathArguments.size());
352 final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(rootNode, relPath);
353 if (possibleNode.isPresent()) {
354 return possibleNode.get();
361 private static NormalizedNode<?, ?> getNodeReferencedByRelativeLeafref(final RevisionAwareXPath xpath,
362 final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext,
363 final TypedDataSchemaNode correspondingSchemaNode) {
364 NormalizedNodeContext relativeNodeContext = currentNodeContext;
365 final StringBuilder xPathStringBuilder = new StringBuilder(xpath.toString());
366 // strip the relative path of all ../ at the beginning
367 while (xPathStringBuilder.indexOf("../") == 0) {
368 xPathStringBuilder.delete(0, 3);
369 relativeNodeContext = relativeNodeContext.getParent();
372 // add / to the beginning of the path so that it can be processed the same way as an absolute path
373 xPathStringBuilder.insert(0, '/');
374 final LeafrefXPathStringParsingPathArgumentBuilder builder = new LeafrefXPathStringParsingPathArgumentBuilder(
375 xPathStringBuilder.toString(), schemaContext, correspondingSchemaNode, currentNodeContext);
376 final List<PathArgument> pathArguments = builder.build();
377 final NormalizedNode<?, ?> relativeNode = relativeNodeContext.getNode();
378 final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(relativeNode, pathArguments);
379 if (possibleNode.isPresent()) {
380 return possibleNode.get();
386 private static LeafSetEntryNode<?> getReferencedLeafSetEntryNode(final LeafSetNode<?> referencedNode,
387 final Object currentNodeValue) {
388 for (final LeafSetEntryNode<?> entryNode : referencedNode.getValue()) {
389 if (currentNodeValue.equals(entryNode.getValue())) {
398 private static boolean containsBit(final BitsTypeDefinition bitsType, final String bitName) {
399 for (BitsTypeDefinition.Bit bit : bitsType.getBits()) {
400 if (bitName.equals(bit.getName())) {
408 private static int getEnumValue(final EnumTypeDefinition enumerationType, final String enumName) {
409 for (final EnumTypeDefinition.EnumPair enumPair : enumerationType.getValues()) {
410 if (enumName.equals(enumPair.getName())) {
411 return enumPair.getValue();
415 throw new IllegalStateException(String.format("Enum %s does not belong to enumeration %s.",
416 enumName, enumerationType));
419 private static SchemaContext getSchemaContext(final NormalizedNodeContext normalizedNodeContext) {
420 final ContextSupport contextSupport = normalizedNodeContext.getContextSupport();
421 Verify.verify(contextSupport instanceof NormalizedNodeContextSupport, "Unhandled context support %s",
422 contextSupport.getClass());
423 return ((NormalizedNodeContextSupport) contextSupport).getSchemaContext();
426 private static TypedDataSchemaNode getCorrespondingTypedSchemaNode(final NormalizedNodeContext currentNodeContext) {
427 final DataSchemaNode schemaNode = currentNodeContext.getSchema().getDataSchemaNode();
428 Preconditions.checkState(schemaNode instanceof TypedDataSchemaNode, "Node %s must be a leaf or a leaf-list.",
429 currentNodeContext.getNode());
430 return (TypedDataSchemaNode) schemaNode;