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;
15 import org.opendaylight.yangtools.yang.common.QName;
16 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
17 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
18 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
19 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
20 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
21 import org.opendaylight.yangtools.yang.model.api.Module;
22 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
23 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
24 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
25 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
26 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
27 import org.opendaylight.yangtools.yang.model.api.RpcDefinition
28 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition
29 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode
31 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition
35 * The Schema Context Util contains support methods for searching through Schema Context modules for specified schema
36 * nodes via Schema Path or Revision Aware XPath. The Schema Context Util is designed as mixin,
37 * so it is not instantiable.
39 * @author Lukas Sedlak <lsedlak@cisco.com>
41 public class SchemaContextUtil {
47 * Method attempts to find DataSchemaNode in Schema Context via specified Schema Path. The returned
48 * DataSchemaNode from method will be the node at the end of the SchemaPath. If the DataSchemaNode is not present
49 * in the Schema Context the method will return <code>null</code>.
51 * In case that Schema Context or Schema Path are not specified correctly (i.e. contains <code>null</code>
52 * values) the method will return IllegalArgumentException.
54 * @throws IllegalArgumentException
59 * Schema Path to search for
60 * @return DataSchemaNode from the end of the Schema Path or
61 * <code>null</code> if the Node is not present.
63 public static def SchemaNode findDataSchemaNode( SchemaContext context, SchemaPath schemaPath) {
64 if (context === null) {
65 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
67 if (schemaPath === null) {
68 throw new IllegalArgumentException("Schema Path reference cannot be NULL");
70 val prefixedPath = (schemaPath.getPath());
71 if (prefixedPath != null) {
72 return findNodeInSchemaContext(context,prefixedPath);
78 * Method attempts to find DataSchemaNode inside of provided Schema Context and Yang Module accordingly to
79 * Non-conditional Revision Aware XPath. The specified Module MUST be present in Schema Context otherwise the
80 * operation would fail and return <code>null</code>.
82 * The Revision Aware XPath MUST be specified WITHOUT the conditional statement (i.e. without [cond]) in path,
83 * because in this state the Schema Context is completely unaware of data state and will be not able to properly
84 * resolve XPath. If the XPath contains condition the method will return IllegalArgumentException.
86 * In case that Schema Context or Module or Revision Aware XPath contains <code>null</code> references the method
87 * will throw IllegalArgumentException
89 * If the Revision Aware XPath is correct and desired Data Schema Node is present in Yang module or in depending
90 * module in Schema Context the method will return specified Data Schema Node, otherwise the operation will fail
91 * and method will return <code>null</code>.
93 * @throws IllegalArgumentException
95 * @param context Schema Context
96 * @param module Yang Module
97 * @param nonCondXPath Non Conditional Revision Aware XPath
98 * @return Returns Data Schema Node for specified Schema Context for given Non-conditional Revision Aware XPath,
99 * or <code>null</code> if the DataSchemaNode is not present in Schema Context.
101 public static def SchemaNode findDataSchemaNode( SchemaContext context, Module module,
102 RevisionAwareXPath nonCondXPath) {
103 if (context === null) {
104 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
106 if (module === null) {
107 throw new IllegalArgumentException("Module reference cannot be NULL!");
109 if (nonCondXPath === null) {
110 throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
113 val strXPath = nonCondXPath.toString();
114 if (strXPath != null) {
115 if (strXPath.contains("[")) {
116 throw new IllegalArgumentException("Revision Aware XPath cannot contains condition!");
118 if (nonCondXPath.isAbsolute()) {
119 val qnamedPath = xpathToQNamePath(context, module, strXPath);
120 if (qnamedPath != null) {
121 return findNodeInSchemaContext(context,qnamedPath);
129 * Method attempts to find DataSchemaNode inside of provided Schema Context and Yang Module accordingly to
130 * Non-conditional relative Revision Aware XPath. The specified Module MUST be present in Schema Context otherwise
131 * the operation would fail and return <code>null</code>.
133 * The relative Revision Aware XPath MUST be specified WITHOUT the conditional statement (i.e. without [cond]) in
134 * path, because in this state the Schema Context is completely unaware of data state and will be not able to
135 * properly resolve XPath. If the XPath contains condition the method will return IllegalArgumentException.
137 * The Actual Schema Node MUST be specified correctly because from this Schema Node will search starts. If the
138 * Actual Schema Node is not correct the operation will simply fail, because it will be unable to find desired
141 * In case that Schema Context or Module or Actual Schema Node or relative Revision Aware XPath contains
142 * <code>null</code> references the method will throw IllegalArgumentException
144 * If the Revision Aware XPath doesn't have flag <code>isAbsolute == false</code> the method will
145 * throw IllegalArgumentException.
147 * If the relative Revision Aware XPath is correct and desired Data Schema Node is present in Yang module or in
148 * depending module in Schema Context the method will return specified Data Schema Node,
149 * otherwise the operation will fail
150 * and method will return <code>null</code>.
152 * @throws IllegalArgumentException
154 * @param context Schema Context
155 * @param module Yang Module
156 * @param actualSchemaNode Actual Schema Node
157 * @param relativeXPath Relative Non Conditional Revision Aware XPath
158 * @return DataSchemaNode if is present in specified Schema Context for given relative Revision Aware XPath,
159 * otherwise will return <code>null</code>.
161 public static def SchemaNode findDataSchemaNodeForRelativeXPath( SchemaContext context, Module module,
162 SchemaNode actualSchemaNode, RevisionAwareXPath relativeXPath) {
163 if (context === null) {
164 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
166 if (module === null) {
167 throw new IllegalArgumentException("Module reference cannot be NULL!");
169 if (actualSchemaNode === null) {
170 throw new IllegalArgumentException("Actual Schema Node reference cannot be NULL!");
172 if (relativeXPath === null) {
173 throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
175 if (relativeXPath.isAbsolute()) {
176 throw new IllegalArgumentException("Revision Aware XPath MUST be relative i.e. MUST contains ../, "
177 + "for non relative Revision Aware XPath use findDataSchemaNode method!");
180 val actualNodePath = actualSchemaNode.getPath();
181 if (actualNodePath != null) {
182 val qnamePath = resolveRelativeXPath(context, module, relativeXPath, actualSchemaNode);
184 if (qnamePath != null) {
185 return findNodeInSchemaContext(context,qnamePath);
192 * Returns parent Yang Module for specified Schema Context in which Schema Node is declared. If the Schema Node
193 * is not present in Schema Context the operation will return <code>null</code>.
195 * If Schema Context or Schema Node contains <code>null</code> references the method will throw IllegalArgumentException
197 * @throws IllegalArgumentException
199 * @param context Schema Context
200 * @param schemaNode Schema Node
201 * @return Yang Module for specified Schema Context and Schema Node, if Schema Node is NOT present,
202 * the method will returns <code>null</code>
204 public static def Module findParentModule( SchemaContext context, SchemaNode schemaNode) {
205 if (context === null) {
206 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
208 if (schemaNode === null) {
209 throw new IllegalArgumentException("Schema Node cannot be NULL!");
212 val schemaPath = schemaNode.getPath();
213 if (schemaPath === null) {
214 throw new IllegalStateException("Schema Path for Schema Node is not "
215 + "set properly (Schema Path is NULL)");
217 val qnamedPath = schemaPath.path;
218 if (qnamedPath === null || qnamedPath.empty) {
219 throw new IllegalStateException("Schema Path contains invalid state of path parts."
220 + "The Schema Path MUST contain at least ONE QName which defines namespace and Local name"
223 val qname = qnamedPath.get(qnamedPath.size() - 1);
224 return context.findModuleByNamespaceAndRevision(qname.namespace,qname.revision);
228 * Method will attempt to find DataSchemaNode from specified Module and Queue of QNames through the Schema
229 * Context. The QNamed path could be defined across multiple modules in Schema Context so the method is called
230 * recursively. If the QNamed path contains QNames that are not part of any Module or Schema Context Path the
231 * operation will fail and returns <code>null</code>
233 * If Schema Context, Module or Queue of QNames refers to <code>null</code> values,
234 * the method will throws IllegalArgumentException
236 * @throws IllegalArgumentException
238 * @param context Schema Context
239 * @param module Yang Module
240 * @param qnamedPath Queue of QNames
241 * @return DataSchemaNode if is present in Module(s) for specified Schema Context and given QNamed Path,
242 * otherwise will return <code>null</code>.
244 private static def SchemaNode findSchemaNodeForGivenPath( SchemaContext context, Module module,
245 Queue<QName> qnamedPath) {
246 if (context === null) {
247 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
249 if (module === null) {
250 throw new IllegalArgumentException("Module reference cannot be NULL!");
252 if (module.getNamespace() === null) {
253 throw new IllegalArgumentException("Namespace for Module cannot contains NULL reference!");
255 if (qnamedPath === null || qnamedPath.isEmpty()) {
256 throw new IllegalStateException("Schema Path contains invalid state of path parts."
257 + "The Schema Path MUST contain at least ONE QName which defines namespace and Local name"
261 var DataNodeContainer nextNode = module;
262 val moduleNamespace = module.getNamespace();
264 var QName childNodeQName;
265 var SchemaNode schemaNode = null;
266 while ((nextNode != null) && !qnamedPath.isEmpty()) {
267 childNodeQName = qnamedPath.peek();
268 if (childNodeQName != null) {
269 schemaNode = nextNode.getDataChildByName(childNodeQName.getLocalName());
270 if(schemaNode === null && nextNode instanceof Module) {
271 schemaNode = (nextNode as Module).getNotificationByName(childNodeQName);
273 if(schemaNode === null && nextNode instanceof Module) {
276 val URI childNamespace = childNodeQName.getNamespace();
277 val Date childRevision = childNodeQName.getRevision();
279 if (schemaNode != null) {
280 if (schemaNode instanceof ContainerSchemaNode) {
281 nextNode = schemaNode as ContainerSchemaNode;
282 } else if (schemaNode instanceof ListSchemaNode) {
283 nextNode = schemaNode as ListSchemaNode;
284 } else if (schemaNode instanceof ChoiceNode) {
285 val choice = schemaNode as ChoiceNode;
287 if (!qnamedPath.isEmpty()) {
288 childNodeQName = qnamedPath.peek();
289 nextNode = choice.getCaseNodeByName(childNodeQName);
290 schemaNode = nextNode as DataSchemaNode;
295 } else if (!childNamespace.equals(moduleNamespace)) {
296 val Module nextModule = context.findModuleByNamespaceAndRevision(childNamespace,childRevision);
297 schemaNode = findSchemaNodeForGivenPath(context, nextModule, qnamedPath);
307 public static def SchemaNode findNodeInSchemaContext(SchemaContext context, List<QName> path) {
308 val current = path.get(0);
309 val module = context.findModuleByNamespaceAndRevision(current.namespace,current.revision);
310 if(module === null) return null;
311 return findNodeInModule(module,path);
314 public static def GroupingDefinition findGrouping(SchemaContext context, Module module, List<QName> path) {
315 var first = path.get(0);
316 var firstPrefix = first.getPrefix();
317 var Module m = context.findModuleByNamespace(first.namespace).iterator().next();
318 var DataNodeContainer currentParent = m;
320 var boolean found = false;
321 var DataNodeContainer node = currentParent.getDataChildByName(qname.localName) as DataNodeContainer;
323 var Set<GroupingDefinition> groupings = currentParent.getGroupings();
324 for (gr : groupings) {
325 if(gr.getQName().localName.equals(qname.localName)) {
332 currentParent = node;
335 throw new IllegalArgumentException("Failed to find referenced grouping: " + path + "(" + qname.localName + ")");
339 return currentParent as GroupingDefinition;
342 private static def SchemaNode findNodeInModule(Module module, List<QName> path) {
343 val current = path.get(0);
344 var SchemaNode node = module.getDataChildByName(current);
345 if (node != null) return findNode(node as DataSchemaNode,path.nextLevel);
346 node = module.getRpcByName(current);
347 if (node != null) return findNodeInRpc(node as RpcDefinition,path.nextLevel)
348 node = module.getNotificationByName(current);
349 if (node != null) return findNodeInNotification(node as NotificationDefinition,path.nextLevel)
353 private static def SchemaNode findNodeInRpc(RpcDefinition rpc,List<QName> path) {
354 if(path.empty) return rpc;
355 val current = path.get(0);
356 switch (current.localName) {
357 case "input": return findNode(rpc.input,path.nextLevel)
358 case "output": return findNode(rpc.output,path.nextLevel)
363 private static def SchemaNode findNodeInNotification(NotificationDefinition rpc,List<QName> path) {
364 if(path.empty) return rpc;
365 val current = path.get(0);
366 val node = rpc.getDataChildByName(current)
367 if(node != null) return findNode(node,path.nextLevel)
371 private static dispatch def SchemaNode findNode(ChoiceNode parent,List<QName> path) {
372 if(path.empty) return parent;
373 val current = path.get(0);
374 val node = parent.getCaseNodeByName(current)
375 if (node != null) return findNodeInCase(node,path.nextLevel)
379 private static dispatch def SchemaNode findNode(ContainerSchemaNode parent,List<QName> path) {
380 if(path.empty) return parent;
381 val current = path.get(0);
382 val node = parent.getDataChildByName(current)
383 if (node != null) return findNode(node,path.nextLevel)
387 private static dispatch def SchemaNode findNode(ListSchemaNode parent,List<QName> path) {
388 if(path.empty) return parent;
389 val current = path.get(0);
390 val node = parent.getDataChildByName(current)
391 if (node != null) return findNode(node,path.nextLevel)
395 private static dispatch def SchemaNode findNode(DataSchemaNode parent,List<QName> path){
399 throw new IllegalArgumentException("Path nesting violation");
403 public static def SchemaNode findNodeInCase(ChoiceCaseNode parent,List<QName> path) {
404 if(path.empty) return parent;
405 val current = path.get(0);
406 val node = parent.getDataChildByName(current)
407 if (node != null) return findNode(node,path.nextLevel)
412 public static def RpcDefinition getRpcByName(Module module, QName name) {
413 for(notification : module.rpcs) {
414 if(notification.QName == name) {
422 private static def nextLevel(List<QName> path){
423 return path.subList(1,path.size)
426 public static def NotificationDefinition getNotificationByName(Module module, QName name) {
427 for(notification : module.notifications) {
428 if(notification.QName == name) {
436 * Transforms string representation of XPath to Queue of QNames. The XPath is split by "/" and for each part of
437 * XPath is assigned correct module in Schema Path.
439 * If Schema Context, Parent Module or XPath string contains <code>null</code> values,
440 * the method will throws IllegalArgumentException
442 * @throws IllegalArgumentException
444 * @param context Schema Context
445 * @param parentModule Parent Module
446 * @param xpath XPath String
447 * @return return a list of QName
449 private static def xpathToQNamePath( SchemaContext context, Module parentModule,
451 if (context === null) {
452 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
454 if (parentModule === null) {
455 throw new IllegalArgumentException("Parent Module reference cannot be NULL!");
457 if (xpath === null) {
458 throw new IllegalArgumentException("XPath string reference cannot be NULL!");
461 val path = new LinkedList<QName>();
462 val String[] prefixedPath = xpath.split("/");
463 for (pathComponent : prefixedPath) {
464 if (!pathComponent.isEmpty()) {
465 path.add(stringPathPartToQName(context, parentModule, pathComponent));
472 * Transforms part of Prefixed Path as java String to QName.
474 * If the string contains module prefix separated by ":" (i.e. mod:container) this module is provided from from
475 * Parent Module list of imports. If the Prefixed module is present in Schema Context the QName can be
478 * If the Prefixed Path Part does not contains prefix the Parent's Module namespace is taken for construction of
481 * If Schema Context, Parent Module or Prefixed Path Part refers to <code>null</code> the method will throw
482 * IllegalArgumentException
484 * @throws IllegalArgumentException
486 * @param context Schema Context
487 * @param parentModule Parent Module
488 * @param prefixedPathPart Prefixed Path Part string
489 * @return QName from prefixed Path Part String.
491 private static def QName stringPathPartToQName( SchemaContext context, Module parentModule,
492 String prefixedPathPart) {
493 if (context === null) {
494 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
496 if (parentModule === null) {
497 throw new IllegalArgumentException("Parent Module reference cannot be NULL!");
499 if (prefixedPathPart === null) {
500 throw new IllegalArgumentException("Prefixed Path Part cannot be NULL!");
503 if (prefixedPathPart.contains(":")) {
504 val String[] prefixedName = prefixedPathPart.split(":");
505 val module = resolveModuleForPrefix(context, parentModule, prefixedName.get(0));
506 if (module != null) {
507 return new QName(module.getNamespace(), module.getRevision(), prefixedName.get(1));
510 return new QName(parentModule.getNamespace(), parentModule.getRevision(), prefixedPathPart);
516 * Method will attempt to resolve and provide Module reference for specified module prefix. Each Yang module
517 * could contains multiple imports which MUST be associated with corresponding module prefix. The method simply
518 * looks into module imports and returns the module that is bounded with specified prefix. If the prefix is not
519 * present in module or the prefixed module is not present in specified Schema Context,
520 * the method will return <code>null</code>.
522 * If String prefix is the same as prefix of the specified Module the reference to this module is returned.
524 * If Schema Context, Module or Prefix are referring to <code>null</code> the method will return
525 * IllegalArgumentException
527 * @throws IllegalArgumentException
529 * @param context Schema Context
530 * @param module Yang Module
531 * @param prefix Module Prefix
532 * @return Module for given prefix in specified Schema Context if is present, otherwise returns <code>null</code>
534 private static def Module resolveModuleForPrefix( SchemaContext context, Module module, String prefix) {
535 if (context === null) {
536 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
538 if (module === null) {
539 throw new IllegalArgumentException("Module reference cannot be NULL!");
541 if (prefix === null) {
542 throw new IllegalArgumentException("Prefix string cannot be NULL!");
545 if (prefix.equals(module.getPrefix())) {
549 val imports = module.getImports();
550 for ( ModuleImport mi : imports) {
551 if (prefix.equals(mi.getPrefix())) {
552 return context.findModuleByName(mi.getModuleName(), mi.getRevision());
559 * @throws IllegalArgumentException
561 * @param context Schema Context
562 * @param module Yang Module
563 * @param relativeXPath Non conditional Revision Aware Relative XPath
564 * @param leafrefSchemaPath Schema Path for Leafref
565 * @return list of QName
567 private static def resolveRelativeXPath( SchemaContext context, Module module,
568 RevisionAwareXPath relativeXPath, SchemaNode leafrefParentNode) {
570 if (context === null) {
571 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
573 if (module === null) {
574 throw new IllegalArgumentException("Module reference cannot be NULL!");
576 if (relativeXPath === null) {
577 throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
579 if (relativeXPath.isAbsolute()) {
580 throw new IllegalArgumentException("Revision Aware XPath MUST be relative i.e. MUST contains ../, "
581 + "for non relative Revision Aware XPath use findDataSchemaNode method!");
583 if (leafrefParentNode.getPath() === null) {
584 throw new IllegalArgumentException("Schema Path reference for Leafref cannot be NULL!");
586 val absolutePath = new LinkedList<QName>();
587 val String strXPath = relativeXPath.toString();
588 if (strXPath != null) {
589 val String[] xpaths = strXPath.split("/");
590 if (xpaths != null) {
591 var int colCount = 0;
592 while (xpaths.get(colCount).contains("..")) {
593 colCount = colCount+ 1;
595 val path = leafrefParentNode.getPath().getPath();
597 val int lenght = path.size() - colCount;
598 absolutePath.addAll(path.subList(0,lenght));
600 xpaths.subList(colCount,xpaths.length).map[stringPathPartToQName(context, module,it)]