2 * Copyright (c) 2013 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.model.util;
11 import java.util.LinkedList;
12 import java.util.List;
13 import java.util.Queue;
16 import org.opendaylight.yangtools.yang.common.QName;
17 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
18 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
19 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
20 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
21 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
22 import org.opendaylight.yangtools.yang.model.api.Module;
23 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
24 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
25 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
26 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
28 import org.opendaylight.yangtools.yang.model.api.RpcDefinition
29 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition
30 import java.io.ObjectOutputStream.PutField
31 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode
34 * The Schema Context Util contains support methods for searching through Schema Context modules for specified schema
35 * nodes via Schema Path or Revision Aware XPath. The Schema Context Util is designed as mixin,
36 * so it is not instantiable.
38 * @author Lukas Sedlak <lsedlak@cisco.com>
40 public class SchemaContextUtil {
46 * Method attempts to find DataSchemaNode in Schema Context via specified Schema Path. The returned
47 * DataSchemaNode from method will be the node at the end of the SchemaPath. If the DataSchemaNode is not present
48 * in the Schema Context the method will return <code>null</code>.
50 * In case that Schema Context or Schema Path are not specified correctly (i.e. contains <code>null</code>
51 * values) the method will return IllegalArgumentException.
53 * @throws IllegalArgumentException
58 * Schema Path to search for
59 * @return DataSchemaNode from the end of the Schema Path or
60 * <code>null</code> if the Node is not present.
62 public static def SchemaNode findDataSchemaNode( SchemaContext context, SchemaPath schemaPath) {
63 if (context == null) {
64 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
66 if (schemaPath == null) {
67 throw new IllegalArgumentException("Schema Path reference cannot be NULL");
69 val prefixedPath = (schemaPath.getPath());
70 if (prefixedPath != null) {
71 return findNodeInSchemaContext(context,prefixedPath);
77 * Method attempts to find DataSchemaNode inside of provided Schema Context and Yang Module accordingly to
78 * Non-conditional Revision Aware XPath. The specified Module MUST be present in Schema Context otherwise the
79 * operation would fail and return <code>null</code>.
81 * The Revision Aware XPath MUST be specified WITHOUT the conditional statement (i.e. without [cond]) in path,
82 * because in this state the Schema Context is completely unaware of data state and will be not able to properly
83 * resolve XPath. If the XPath contains condition the method will return IllegalArgumentException.
85 * In case that Schema Context or Module or Revision Aware XPath contains <code>null</code> references the method
86 * will throw IllegalArgumentException
88 * If the Revision Aware XPath is correct and desired Data Schema Node is present in Yang module or in depending
89 * module in Schema Context the method will return specified Data Schema Node, otherwise the operation will fail
90 * and method will return <code>null</code>.
92 * @throws IllegalArgumentException
94 * @param context Schema Context
95 * @param module Yang Module
96 * @param nonCondXPath Non Conditional Revision Aware XPath
97 * @return Returns Data Schema Node for specified Schema Context for given Non-conditional Revision Aware XPath,
98 * or <code>null</code> if the DataSchemaNode is not present in Schema Context.
100 public static def SchemaNode findDataSchemaNode( SchemaContext context, Module module,
101 RevisionAwareXPath nonCondXPath) {
102 if (context == null) {
103 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
105 if (module == null) {
106 throw new IllegalArgumentException("Module reference cannot be NULL!");
108 if (nonCondXPath == null) {
109 throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
112 val strXPath = nonCondXPath.toString();
113 if (strXPath != null) {
114 if (strXPath.contains("[")) {
115 throw new IllegalArgumentException("Revision Aware XPath cannot contains condition!");
117 if (nonCondXPath.isAbsolute()) {
118 val qnamedPath = xpathToQNamePath(context, module, strXPath);
119 if (qnamedPath != null) {
120 return findNodeInSchemaContext(context,qnamedPath);
128 * Method attempts to find DataSchemaNode inside of provided Schema Context and Yang Module accordingly to
129 * Non-conditional relative Revision Aware XPath. The specified Module MUST be present in Schema Context otherwise
130 * the operation would fail and return <code>null</code>.
132 * The relative Revision Aware XPath MUST be specified WITHOUT the conditional statement (i.e. without [cond]) in
133 * path, because in this state the Schema Context is completely unaware of data state and will be not able to
134 * properly resolve XPath. If the XPath contains condition the method will return IllegalArgumentException.
136 * The Actual Schema Node MUST be specified correctly because from this Schema Node will search starts. If the
137 * Actual Schema Node is not correct the operation will simply fail, because it will be unable to find desired
140 * In case that Schema Context or Module or Actual Schema Node or relative Revision Aware XPath contains
141 * <code>null</code> references the method will throw IllegalArgumentException
143 * If the Revision Aware XPath doesn't have flag <code>isAbsolute == false</code> the method will
144 * throw IllegalArgumentException.
146 * If the relative Revision Aware XPath is correct and desired Data Schema Node is present in Yang module or in
147 * depending module in Schema Context the method will return specified Data Schema Node,
148 * otherwise the operation will fail
149 * and method will return <code>null</code>.
151 * @throws IllegalArgumentException
153 * @param context Schema Context
154 * @param module Yang Module
155 * @param actualSchemaNode Actual Schema Node
156 * @param relativeXPath Relative Non Conditional Revision Aware XPath
157 * @return DataSchemaNode if is present in specified Schema Context for given relative Revision Aware XPath,
158 * otherwise will return <code>null</code>.
160 public static def SchemaNode findDataSchemaNodeForRelativeXPath( SchemaContext context, Module module,
161 SchemaNode actualSchemaNode, RevisionAwareXPath relativeXPath) {
162 if (context == null) {
163 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
165 if (module == null) {
166 throw new IllegalArgumentException("Module reference cannot be NULL!");
168 if (actualSchemaNode == null) {
169 throw new IllegalArgumentException("Actual Schema Node reference cannot be NULL!");
171 if (relativeXPath == null) {
172 throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
174 if (relativeXPath.isAbsolute()) {
175 throw new IllegalArgumentException("Revision Aware XPath MUST be relative i.e. MUST contains ../, "
176 + "for non relative Revision Aware XPath use findDataSchemaNode method!");
179 val actualNodePath = actualSchemaNode.getPath();
180 if (actualNodePath != null) {
181 val qnamePath = resolveRelativeXPath(context, module, relativeXPath, actualSchemaNode);
183 if (qnamePath != null) {
184 return findNodeInSchemaContext(context,qnamePath);
191 * Returns parent Yang Module for specified Schema Context in which Schema Node is declared. If the Schema Node
192 * is not present in Schema Context the operation will return <code>null</code>.
194 * If Schema Context or Schema Node contains <code>null</code> references the method will throw IllegalArgumentException
196 * @throws IllegalArgumentException
198 * @param context Schema Context
199 * @param schemaNode Schema Node
200 * @return Yang Module for specified Schema Context and Schema Node, if Schema Node is NOT present,
201 * the method will returns <code>null</code>
203 public static def Module findParentModule( SchemaContext context, SchemaNode schemaNode) {
204 if (context == null) {
205 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
207 if (schemaNode == null) {
208 throw new IllegalArgumentException("Schema Node cannot be NULL!");
211 val schemaPath = schemaNode.getPath();
212 if (schemaPath == null) {
213 throw new IllegalStateException("Schema Path for Schema Node is not "
214 + "set properly (Schema Path is NULL)");
216 val qnamedPath = schemaPath.getPath();
217 if (qnamedPath == null || qnamedPath.isEmpty()) {
218 throw new IllegalStateException("Schema Path contains invalid state of path parts."
219 + "The Schema Path MUST contain at least ONE QName which defines namespace and Local name"
222 val qname = qnamedPath.get(qnamedPath.size() - 1);
223 return context.findModuleByNamespace(qname.getNamespace());
227 * Method will attempt to find DataSchemaNode from specified Module and Queue of QNames through the Schema
228 * Context. The QNamed path could be defined across multiple modules in Schema Context so the method is called
229 * recursively. If the QNamed path contains QNames that are not part of any Module or Schema Context Path the
230 * operation will fail and returns <code>null</code>
232 * If Schema Context, Module or Queue of QNames refers to <code>null</code> values,
233 * the method will throws IllegalArgumentException
235 * @throws IllegalArgumentException
237 * @param context Schema Context
238 * @param module Yang Module
239 * @param qnamedPath Queue of QNames
240 * @return DataSchemaNode if is present in Module(s) for specified Schema Context and given QNamed Path,
241 * otherwise will return <code>null</code>.
243 private static def SchemaNode findSchemaNodeForGivenPath( SchemaContext context, Module module,
244 Queue<QName> qnamedPath) {
245 if (context == null) {
246 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
248 if (module == null) {
249 throw new IllegalArgumentException("Module reference cannot be NULL!");
251 if (module.getNamespace() == null) {
252 throw new IllegalArgumentException("Namespace for Module cannot contains NULL reference!");
254 if (qnamedPath == null || qnamedPath.isEmpty()) {
255 throw new IllegalStateException("Schema Path contains invalid state of path parts."
256 + "The Schema Path MUST contain at least ONE QName which defines namespace and Local name"
260 var DataNodeContainer nextNode = module;
261 val moduleNamespace = module.getNamespace();
263 var QName childNodeQName;
264 var SchemaNode schemaNode = null;
265 while ((nextNode != null) && !qnamedPath.isEmpty()) {
266 childNodeQName = qnamedPath.peek();
267 if (childNodeQName != null) {
268 val URI childNodeNamespace = childNodeQName.getNamespace();
270 schemaNode = nextNode.getDataChildByName(childNodeQName.getLocalName());
271 if(schemaNode == null && nextNode instanceof Module) {
272 schemaNode = (nextNode as Module).getNotificationByName(childNodeQName);
274 if(schemaNode == null && nextNode instanceof Module) {
277 if (schemaNode != null) {
278 if (schemaNode instanceof ContainerSchemaNode) {
279 nextNode = schemaNode as ContainerSchemaNode;
280 } else if (schemaNode instanceof ListSchemaNode) {
281 nextNode = schemaNode as ListSchemaNode;
282 } else if (schemaNode instanceof ChoiceNode) {
283 val choice = schemaNode as ChoiceNode;
285 if (!qnamedPath.isEmpty()) {
286 childNodeQName = qnamedPath.peek();
287 nextNode = choice.getCaseNodeByName(childNodeQName);
288 schemaNode = nextNode as DataSchemaNode;
293 } else if (!childNodeNamespace.equals(moduleNamespace)) {
294 val Module nextModule = context.findModuleByNamespace(childNodeNamespace);
295 schemaNode = findSchemaNodeForGivenPath(context, nextModule, qnamedPath);
305 private static def SchemaNode findNodeInSchemaContext(SchemaContext context, List<QName> path) {
306 val current = path.get(0);
307 val module = context.findModuleByNamespace(current.namespace);
308 if(module == null) return null;
309 return findNodeInModule(module,path);
312 private static def SchemaNode findNodeInModule(Module module, List<QName> path) {
313 val current = path.get(0);
314 var SchemaNode node = module.getDataChildByName(current);
315 if (node != null) return findNode(node as DataSchemaNode,path.nextLevel);
316 node = module.getRpcByName(current);
317 if (node != null) return findNodeInRpc(node as RpcDefinition,path.nextLevel)
318 node = module.getNotificationByName(current);
319 if (node != null) return findNodeInNotification(node as NotificationDefinition,path.nextLevel)
323 private static def SchemaNode findNodeInRpc(RpcDefinition rpc,List<QName> path) {
324 if(path.empty) return rpc;
325 val current = path.get(0);
326 switch (current.localName) {
327 case "input": return findNode(rpc.input,path.nextLevel)
328 case "output": return findNode(rpc.output,path.nextLevel)
333 private static def SchemaNode findNodeInNotification(NotificationDefinition rpc,List<QName> path) {
334 if(path.empty) return rpc;
335 val current = path.get(0);
336 val node = rpc.getDataChildByName(current)
337 if(node != null) return findNode(node,path.nextLevel)
341 private static dispatch def SchemaNode findNode(ChoiceNode parent,List<QName> path) {
342 if(path.empty) return parent;
343 val current = path.get(0);
344 val node = parent.getCaseNodeByName(current)
345 if (node != null) return findNodeInCase(node,path.nextLevel)
349 private static dispatch def SchemaNode findNode(ContainerSchemaNode parent,List<QName> path) {
350 if(path.empty) return parent;
351 val current = path.get(0);
352 val node = parent.getDataChildByName(current)
353 if (node != null) return findNode(node,path.nextLevel)
357 private static dispatch def SchemaNode findNode(ListSchemaNode parent,List<QName> path) {
358 if(path.empty) return parent;
359 val current = path.get(0);
360 val node = parent.getDataChildByName(current)
361 if (node != null) return findNode(node,path.nextLevel)
365 private static dispatch def SchemaNode findNode(DataSchemaNode parent,List<QName> path){
369 throw new IllegalArgumentException("Path nesting violation");
373 public static def SchemaNode findNodeInCase(ChoiceCaseNode parent,List<QName> path) {
374 if(path.empty) return parent;
375 val current = path.get(0);
376 val node = parent.getDataChildByName(current)
377 if (node != null) return findNode(node,path.nextLevel)
382 public static def RpcDefinition getRpcByName(Module module, QName name) {
383 for(notification : module.rpcs) {
384 if(notification.QName == name) {
392 private static def nextLevel(List<QName> path){
393 return path.subList(1,path.size)
396 public static def NotificationDefinition getNotificationByName(Module module, QName name) {
397 for(notification : module.notifications) {
398 if(notification.QName == name) {
406 * Transforms string representation of XPath to Queue of QNames. The XPath is split by "/" and for each part of
407 * XPath is assigned correct module in Schema Path.
409 * If Schema Context, Parent Module or XPath string contains <code>null</code> values,
410 * the method will throws IllegalArgumentException
412 * @throws IllegalArgumentException
414 * @param context Schema Context
415 * @param parentModule Parent Module
416 * @param xpath XPath String
419 private static def xpathToQNamePath( SchemaContext context, Module parentModule,
421 if (context == null) {
422 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
424 if (parentModule == null) {
425 throw new IllegalArgumentException("Parent Module reference cannot be NULL!");
428 throw new IllegalArgumentException("XPath string reference cannot be NULL!");
431 val path = new LinkedList<QName>();
432 val String[] prefixedPath = xpath.split("/");
433 for (pathComponent : prefixedPath) {
434 if (!pathComponent.isEmpty()) {
435 path.add(stringPathPartToQName(context, parentModule, pathComponent));
442 * Transforms part of Prefixed Path as java String to QName.
444 * If the string contains module prefix separated by ":" (i.e. mod:container) this module is provided from from
445 * Parent Module list of imports. If the Prefixed module is present in Schema Context the QName can be
448 * If the Prefixed Path Part does not contains prefix the Parent's Module namespace is taken for construction of
451 * If Schema Context, Parent Module or Prefixed Path Part refers to <code>null</code> the method will throw
452 * IllegalArgumentException
454 * @throws IllegalArgumentException
456 * @param context Schema Context
457 * @param parentModule Parent Module
458 * @param prefixedPathPart Prefixed Path Part string
459 * @return QName from prefixed Path Part String.
461 private static def QName stringPathPartToQName( SchemaContext context, Module parentModule,
462 String prefixedPathPart) {
463 if (context == null) {
464 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
466 if (parentModule == null) {
467 throw new IllegalArgumentException("Parent Module reference cannot be NULL!");
469 if (prefixedPathPart == null) {
470 throw new IllegalArgumentException("Prefixed Path Part cannot be NULL!");
473 if (prefixedPathPart.contains(":")) {
474 val String[] prefixedName = prefixedPathPart.split(":");
475 val module = resolveModuleForPrefix(context, parentModule, prefixedName.get(0));
476 if (module != null) {
477 return new QName(module.getNamespace(), module.getRevision(), prefixedName.get(1));
480 return new QName(parentModule.getNamespace(), parentModule.getRevision(), prefixedPathPart);
486 * Method will attempt to resolve and provide Module reference for specified module prefix. Each Yang module
487 * could contains multiple imports which MUST be associated with corresponding module prefix. The method simply
488 * looks into module imports and returns the module that is bounded with specified prefix. If the prefix is not
489 * present in module or the prefixed module is not present in specified Schema Context,
490 * the method will return <code>null</code>.
492 * If String prefix is the same as prefix of the specified Module the reference to this module is returned.
494 * If Schema Context, Module or Prefix are referring to <code>null</code> the method will return
495 * IllegalArgumentException
497 * @throws IllegalArgumentException
499 * @param context Schema Context
500 * @param module Yang Module
501 * @param prefix Module Prefix
502 * @return Module for given prefix in specified Schema Context if is present, otherwise returns <code>null</code>
504 private static def Module resolveModuleForPrefix( SchemaContext context, Module module, String prefix) {
505 if (context == null) {
506 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
508 if (module == null) {
509 throw new IllegalArgumentException("Module reference cannot be NULL!");
511 if (prefix == null) {
512 throw new IllegalArgumentException("Prefix string cannot be NULL!");
515 if (prefix.equals(module.getPrefix())) {
519 val imports = module.getImports();
520 for ( ModuleImport mi : imports) {
521 if (prefix.equals(mi.getPrefix())) {
522 return context.findModuleByName(mi.getModuleName(), mi.getRevision());
529 * @throws IllegalArgumentException
531 * @param context Schema Context
532 * @param module Yang Module
533 * @param relativeXPath Non conditional Revision Aware Relative XPath
534 * @param leafrefSchemaPath Schema Path for Leafref
537 private static def resolveRelativeXPath( SchemaContext context, Module module,
538 RevisionAwareXPath relativeXPath, SchemaNode leafrefParentNode) {
540 if (context == null) {
541 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
543 if (module == null) {
544 throw new IllegalArgumentException("Module reference cannot be NULL!");
546 if (relativeXPath == null) {
547 throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
549 if (relativeXPath.isAbsolute()) {
550 throw new IllegalArgumentException("Revision Aware XPath MUST be relative i.e. MUST contains ../, "
551 + "for non relative Revision Aware XPath use findDataSchemaNode method!");
553 if (leafrefParentNode.getPath() == null) {
554 throw new IllegalArgumentException("Schema Path reference for Leafref cannot be NULL!");
556 val absolutePath = new LinkedList<QName>();
557 val String strXPath = relativeXPath.toString();
558 if (strXPath != null) {
559 val String[] xpaths = strXPath.split("/");
560 if (xpaths != null) {
561 var int colCount = 0;
562 while (xpaths.get(colCount).contains("..")) {
563 colCount = colCount+ 1;
565 val path = leafrefParentNode.getPath().getPath();
567 val int lenght = path.size() - colCount;
568 absolutePath.addAll(path.subList(0,lenght));
570 xpaths.subList(colCount,xpaths.length).map[stringPathPartToQName(context, module,it)]