Converted BindingGenerator and ParserUtils to xtend
[yangtools.git] / yang / yang-model-util / src / main / java / org / opendaylight / yangtools / yang / model / util / SchemaContextUtil.xtend
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.yangtools.yang.model.util;
9
10 import java.net.URI;
11 import java.util.LinkedList;
12 import java.util.List;
13 import java.util.Queue;
14 import java.util.Set;
15
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
32
33 /**
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.
37  *
38  * @author Lukas Sedlak <lsedlak@cisco.com>
39  */
40 public  class SchemaContextUtil {
41
42     private new() {
43     }
44
45     /**
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>.
49      * <br>
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.
52      *
53      * @throws IllegalArgumentException
54      *
55      * @param context
56      *            Schema Context
57      * @param schemaPath
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.
61      */
62     public static def SchemaNode findDataSchemaNode( SchemaContext context,  SchemaPath schemaPath) {
63         if (context == null) {
64             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
65         }
66         if (schemaPath == null) {
67             throw new IllegalArgumentException("Schema Path reference cannot be NULL");
68         }
69         val prefixedPath = (schemaPath.getPath());
70         if (prefixedPath != null) {
71             return findNodeInSchemaContext(context,prefixedPath);
72         }
73         return null;
74     }
75
76     /**
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>.
80      * <br>
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.
84      * <br>
85      * In case that Schema Context or Module or Revision Aware XPath contains <code>null</code> references the method
86      * will throw IllegalArgumentException
87      * <br>
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>.
91      *
92      * @throws IllegalArgumentException
93      *
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.
99      */
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!");
104         }
105         if (module == null) {
106             throw new IllegalArgumentException("Module reference cannot be NULL!");
107         }
108         if (nonCondXPath == null) {
109             throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
110         }
111
112          val  strXPath = nonCondXPath.toString();
113         if (strXPath != null) {
114             if (strXPath.contains("[")) {
115                 throw new IllegalArgumentException("Revision Aware XPath cannot contains condition!");
116             }
117             if (nonCondXPath.isAbsolute()) {
118                  val qnamedPath = xpathToQNamePath(context, module, strXPath);
119                 if (qnamedPath != null) {
120                     return findNodeInSchemaContext(context,qnamedPath);
121                 }
122             }
123         }
124         return null;
125     }
126
127     /**
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>.
131      * <br>
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.
135      * <br>
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
138      * DataSchemaNode.
139      * <br>
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
142      * <br>
143      * If the Revision Aware XPath doesn't have flag <code>isAbsolute == false</code> the method will
144      * throw IllegalArgumentException.
145      * <br>
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>.
150      *
151      * @throws IllegalArgumentException
152      *
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>.
159      */
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!");
164         }
165         if (module == null) {
166             throw new IllegalArgumentException("Module reference cannot be NULL!");
167         }
168         if (actualSchemaNode == null) {
169             throw new IllegalArgumentException("Actual Schema Node reference cannot be NULL!");
170         }
171         if (relativeXPath == null) {
172             throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
173         }
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!");
177         }
178
179          val actualNodePath = actualSchemaNode.getPath();
180         if (actualNodePath != null) {
181              val qnamePath = resolveRelativeXPath(context, module, relativeXPath, actualSchemaNode);
182
183             if (qnamePath != null) {
184                 return findNodeInSchemaContext(context,qnamePath);
185             }
186         }
187         return null;
188     }
189
190     /**
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>.
193      * <br>
194      * If Schema Context or Schema Node contains <code>null</code> references the method will throw IllegalArgumentException
195      *
196      * @throws IllegalArgumentException
197      *
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>
202      */
203     public static def Module findParentModule( SchemaContext context,  SchemaNode schemaNode) {
204         if (context == null) {
205             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
206         }
207         if (schemaNode == null) {
208             throw new IllegalArgumentException("Schema Node cannot be NULL!");
209         }
210
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)");
215         }
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"
220                     + "of path.");
221         }
222         val qname = qnamedPath.get(qnamedPath.size() - 1);
223         return context.findModuleByNamespace(qname.getNamespace());
224     }
225
226     /**
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>
231      * <br>
232      * If Schema Context, Module or Queue of QNames refers to <code>null</code> values,
233      * the method will throws IllegalArgumentException
234      *
235      * @throws IllegalArgumentException
236      *
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>.
242      */
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!");
247         }
248         if (module == null) {
249             throw new IllegalArgumentException("Module reference cannot be NULL!");
250         }
251         if (module.getNamespace() == null) {
252             throw new IllegalArgumentException("Namespace for Module cannot contains NULL reference!");
253         }
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"
257                     + "of path.");
258         }
259
260         var DataNodeContainer nextNode = module;
261         val moduleNamespace = module.getNamespace();
262
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();
269
270                 schemaNode = nextNode.getDataChildByName(childNodeQName.getLocalName());
271                 if(schemaNode == null && nextNode instanceof Module) {
272                     schemaNode = (nextNode as Module).getNotificationByName(childNodeQName);
273                 }
274                 if(schemaNode == null && nextNode instanceof Module) {
275                     
276                 }
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;
284                         qnamedPath.poll();
285                         if (!qnamedPath.isEmpty()) {
286                             childNodeQName = qnamedPath.peek();
287                             nextNode = choice.getCaseNodeByName(childNodeQName);
288                             schemaNode = nextNode as DataSchemaNode;
289                         }
290                     } else {
291                         nextNode = null;
292                     }
293                 } else if (!childNodeNamespace.equals(moduleNamespace)) {
294                     val Module nextModule = context.findModuleByNamespace(childNodeNamespace);
295                     schemaNode = findSchemaNodeForGivenPath(context, nextModule, qnamedPath);
296                     return schemaNode;
297                 }
298                 qnamedPath.poll();
299             }
300         }
301         return schemaNode;
302     }
303     
304     
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);
310     }
311     
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)
320         return null
321     }
322      
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)
329         }
330         return null
331     }
332     
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)
338         return null
339     }
340     
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)
346         return null
347     }
348     
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)
354         return null
355     }
356     
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)
362         return null
363     }
364     
365     private static dispatch def SchemaNode findNode(DataSchemaNode parent,List<QName> path){
366         if(path.empty) {
367             return parent
368         } else {
369             throw new IllegalArgumentException("Path nesting violation");
370         }
371     }
372     
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)
378         return null
379     }
380     
381      
382     public static def RpcDefinition getRpcByName(Module module, QName name) {
383         for(notification : module.rpcs) {
384             if(notification.QName == name) {
385                 return notification;
386             }
387         }
388         return null;
389     }
390     
391     
392     private static def nextLevel(List<QName> path){
393         return path.subList(1,path.size)
394     }
395     
396     public static def NotificationDefinition getNotificationByName(Module module, QName name) {
397         for(notification : module.notifications) {
398             if(notification.QName == name) {
399                 return notification;
400             }
401         }
402         return null;
403     }
404
405     /**
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.
408      * <br>
409      * If Schema Context, Parent Module or XPath string contains <code>null</code> values,
410      * the method will throws IllegalArgumentException
411      *
412      * @throws IllegalArgumentException
413      *
414      * @param context Schema Context
415      * @param parentModule Parent Module
416      * @param xpath XPath String
417      * @return
418      */
419     private static def xpathToQNamePath( SchemaContext context,  Module parentModule,
420              String xpath) {
421         if (context == null) {
422             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
423         }
424         if (parentModule == null) {
425             throw new IllegalArgumentException("Parent Module reference cannot be NULL!");
426         }
427         if (xpath == null) {
428             throw new IllegalArgumentException("XPath string reference cannot be NULL!");
429         }
430
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));
436             }
437         }
438         return path;
439     }
440
441     /**
442      * Transforms part of Prefixed Path as java String to QName.
443      * <br>
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
446      * constructed.
447      * <br>
448      * If the Prefixed Path Part does not contains prefix the Parent's Module namespace is taken for construction of
449      * QName.
450      * <br>
451      * If Schema Context, Parent Module or Prefixed Path Part refers to <code>null</code> the method will throw
452      * IllegalArgumentException
453      *
454      * @throws IllegalArgumentException
455      *
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.
460      */
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!");
465         }
466         if (parentModule == null) {
467             throw new IllegalArgumentException("Parent Module reference cannot be NULL!");
468         }
469         if (prefixedPathPart == null) {
470             throw new IllegalArgumentException("Prefixed Path Part cannot be NULL!");
471         }
472
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));
478             }
479         } else {
480             return new QName(parentModule.getNamespace(), parentModule.getRevision(), prefixedPathPart);
481         }
482         return null;
483     }
484
485     /**
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>.
491      * <br>
492      * If String prefix is the same as prefix of the specified Module the reference to this module is returned.
493      * <br>
494      * If Schema Context, Module or Prefix are referring to <code>null</code> the method will return
495      * IllegalArgumentException
496      *
497      * @throws IllegalArgumentException
498      *
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>
503      */
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!");
507         }
508         if (module == null) {
509             throw new IllegalArgumentException("Module reference cannot be NULL!");
510         }
511         if (prefix == null) {
512             throw new IllegalArgumentException("Prefix string cannot be NULL!");
513         }
514
515         if (prefix.equals(module.getPrefix())) {
516             return module;
517         }
518
519         val imports = module.getImports();
520         for ( ModuleImport mi : imports) {
521             if (prefix.equals(mi.getPrefix())) {
522                 return context.findModuleByName(mi.getModuleName(), mi.getRevision());
523             }
524         }
525         return null;
526     }
527
528     /**
529      * @throws IllegalArgumentException
530      *
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
535      * @return
536      */
537     private static def resolveRelativeXPath( SchemaContext context,  Module module,
538              RevisionAwareXPath relativeXPath,  SchemaNode leafrefParentNode) {
539
540         if (context == null) {
541             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
542         }
543         if (module == null) {
544             throw new IllegalArgumentException("Module reference cannot be NULL!");
545         }
546         if (relativeXPath == null) {
547             throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
548         }
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!");
552         }
553         if (leafrefParentNode.getPath() == null) {
554             throw new IllegalArgumentException("Schema Path reference for Leafref cannot be NULL!");
555         }
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;
564                 }
565                 val path = leafrefParentNode.getPath().getPath();
566                 if (path != null) {
567                     val int lenght = path.size() - colCount;
568                     absolutePath.addAll(path.subList(0,lenght));
569                     absolutePath.addAll(
570                         xpaths.subList(colCount,xpaths.length).map[stringPathPartToQName(context, module,it)]
571                     )
572                 }
573             }
574         }
575         return absolutePath;
576     }
577 }