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
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 private 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 private static def SchemaNode findNodeInModule(Module module, List<QName> path) {
315 val current = path.get(0);
316 var SchemaNode node = module.getDataChildByName(current);
317 if (node != null) return findNode(node as DataSchemaNode,path.nextLevel);
318 node = module.getRpcByName(current);
319 if (node != null) return findNodeInRpc(node as RpcDefinition,path.nextLevel)
320 node = module.getNotificationByName(current);
321 if (node != null) return findNodeInNotification(node as NotificationDefinition,path.nextLevel)
325 private static def SchemaNode findNodeInRpc(RpcDefinition rpc,List<QName> path) {
326 if(path.empty) return rpc;
327 val current = path.get(0);
328 switch (current.localName) {
329 case "input": return findNode(rpc.input,path.nextLevel)
330 case "output": return findNode(rpc.output,path.nextLevel)
335 private static def SchemaNode findNodeInNotification(NotificationDefinition rpc,List<QName> path) {
336 if(path.empty) return rpc;
337 val current = path.get(0);
338 val node = rpc.getDataChildByName(current)
339 if(node != null) return findNode(node,path.nextLevel)
343 private static dispatch def SchemaNode findNode(ChoiceNode parent,List<QName> path) {
344 if(path.empty) return parent;
345 val current = path.get(0);
346 val node = parent.getCaseNodeByName(current)
347 if (node != null) return findNodeInCase(node,path.nextLevel)
351 private static dispatch def SchemaNode findNode(ContainerSchemaNode parent,List<QName> path) {
352 if(path.empty) return parent;
353 val current = path.get(0);
354 val node = parent.getDataChildByName(current)
355 if (node != null) return findNode(node,path.nextLevel)
359 private static dispatch def SchemaNode findNode(ListSchemaNode parent,List<QName> path) {
360 if(path.empty) return parent;
361 val current = path.get(0);
362 val node = parent.getDataChildByName(current)
363 if (node != null) return findNode(node,path.nextLevel)
367 private static dispatch def SchemaNode findNode(DataSchemaNode parent,List<QName> path){
371 throw new IllegalArgumentException("Path nesting violation");
375 public static def SchemaNode findNodeInCase(ChoiceCaseNode parent,List<QName> path) {
376 if(path.empty) return parent;
377 val current = path.get(0);
378 val node = parent.getDataChildByName(current)
379 if (node != null) return findNode(node,path.nextLevel)
384 public static def RpcDefinition getRpcByName(Module module, QName name) {
385 for(notification : module.rpcs) {
386 if(notification.QName == name) {
394 private static def nextLevel(List<QName> path){
395 return path.subList(1,path.size)
398 public static def NotificationDefinition getNotificationByName(Module module, QName name) {
399 for(notification : module.notifications) {
400 if(notification.QName == name) {
408 * Transforms string representation of XPath to Queue of QNames. The XPath is split by "/" and for each part of
409 * XPath is assigned correct module in Schema Path.
411 * If Schema Context, Parent Module or XPath string contains <code>null</code> values,
412 * the method will throws IllegalArgumentException
414 * @throws IllegalArgumentException
416 * @param context Schema Context
417 * @param parentModule Parent Module
418 * @param xpath XPath String
421 private static def xpathToQNamePath( SchemaContext context, Module parentModule,
423 if (context === null) {
424 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
426 if (parentModule === null) {
427 throw new IllegalArgumentException("Parent Module reference cannot be NULL!");
429 if (xpath === null) {
430 throw new IllegalArgumentException("XPath string reference cannot be NULL!");
433 val path = new LinkedList<QName>();
434 val String[] prefixedPath = xpath.split("/");
435 for (pathComponent : prefixedPath) {
436 if (!pathComponent.isEmpty()) {
437 path.add(stringPathPartToQName(context, parentModule, pathComponent));
444 * Transforms part of Prefixed Path as java String to QName.
446 * If the string contains module prefix separated by ":" (i.e. mod:container) this module is provided from from
447 * Parent Module list of imports. If the Prefixed module is present in Schema Context the QName can be
450 * If the Prefixed Path Part does not contains prefix the Parent's Module namespace is taken for construction of
453 * If Schema Context, Parent Module or Prefixed Path Part refers to <code>null</code> the method will throw
454 * IllegalArgumentException
456 * @throws IllegalArgumentException
458 * @param context Schema Context
459 * @param parentModule Parent Module
460 * @param prefixedPathPart Prefixed Path Part string
461 * @return QName from prefixed Path Part String.
463 private static def QName stringPathPartToQName( SchemaContext context, Module parentModule,
464 String prefixedPathPart) {
465 if (context === null) {
466 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
468 if (parentModule === null) {
469 throw new IllegalArgumentException("Parent Module reference cannot be NULL!");
471 if (prefixedPathPart === null) {
472 throw new IllegalArgumentException("Prefixed Path Part cannot be NULL!");
475 if (prefixedPathPart.contains(":")) {
476 val String[] prefixedName = prefixedPathPart.split(":");
477 val module = resolveModuleForPrefix(context, parentModule, prefixedName.get(0));
478 if (module != null) {
479 return new QName(module.getNamespace(), module.getRevision(), prefixedName.get(1));
482 return new QName(parentModule.getNamespace(), parentModule.getRevision(), prefixedPathPart);
488 * Method will attempt to resolve and provide Module reference for specified module prefix. Each Yang module
489 * could contains multiple imports which MUST be associated with corresponding module prefix. The method simply
490 * looks into module imports and returns the module that is bounded with specified prefix. If the prefix is not
491 * present in module or the prefixed module is not present in specified Schema Context,
492 * the method will return <code>null</code>.
494 * If String prefix is the same as prefix of the specified Module the reference to this module is returned.
496 * If Schema Context, Module or Prefix are referring to <code>null</code> the method will return
497 * IllegalArgumentException
499 * @throws IllegalArgumentException
501 * @param context Schema Context
502 * @param module Yang Module
503 * @param prefix Module Prefix
504 * @return Module for given prefix in specified Schema Context if is present, otherwise returns <code>null</code>
506 private static def Module resolveModuleForPrefix( SchemaContext context, Module module, String prefix) {
507 if (context === null) {
508 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
510 if (module === null) {
511 throw new IllegalArgumentException("Module reference cannot be NULL!");
513 if (prefix === null) {
514 throw new IllegalArgumentException("Prefix string cannot be NULL!");
517 if (prefix.equals(module.getPrefix())) {
521 val imports = module.getImports();
522 for ( ModuleImport mi : imports) {
523 if (prefix.equals(mi.getPrefix())) {
524 return context.findModuleByName(mi.getModuleName(), mi.getRevision());
531 * @throws IllegalArgumentException
533 * @param context Schema Context
534 * @param module Yang Module
535 * @param relativeXPath Non conditional Revision Aware Relative XPath
536 * @param leafrefSchemaPath Schema Path for Leafref
539 private static def resolveRelativeXPath( SchemaContext context, Module module,
540 RevisionAwareXPath relativeXPath, SchemaNode leafrefParentNode) {
542 if (context === null) {
543 throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
545 if (module === null) {
546 throw new IllegalArgumentException("Module reference cannot be NULL!");
548 if (relativeXPath === null) {
549 throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
551 if (relativeXPath.isAbsolute()) {
552 throw new IllegalArgumentException("Revision Aware XPath MUST be relative i.e. MUST contains ../, "
553 + "for non relative Revision Aware XPath use findDataSchemaNode method!");
555 if (leafrefParentNode.getPath() === null) {
556 throw new IllegalArgumentException("Schema Path reference for Leafref cannot be NULL!");
558 val absolutePath = new LinkedList<QName>();
559 val String strXPath = relativeXPath.toString();
560 if (strXPath != null) {
561 val String[] xpaths = strXPath.split("/");
562 if (xpaths != null) {
563 var int colCount = 0;
564 while (xpaths.get(colCount).contains("..")) {
565 colCount = colCount+ 1;
567 val path = leafrefParentNode.getPath().getPath();
569 val int lenght = path.size() - colCount;
570 absolutePath.addAll(path.subList(0,lenght));
572 xpaths.subList(colCount,xpaths.length).map[stringPathPartToQName(context, module,it)]