Fixed resolution problems in Code Generator
[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 import java.util.Date
33
34 /**
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.
38  *
39  * @author Lukas Sedlak <lsedlak@cisco.com>
40  */
41 public  class SchemaContextUtil {
42
43     private new() {
44     }
45
46     /**
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>.
50      * <br>
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.
53      *
54      * @throws IllegalArgumentException
55      *
56      * @param context
57      *            Schema Context
58      * @param schemaPath
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.
62      */
63     public static def SchemaNode findDataSchemaNode( SchemaContext context,  SchemaPath schemaPath) {
64         if (context === null) {
65             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
66         }
67         if (schemaPath === null) {
68             throw new IllegalArgumentException("Schema Path reference cannot be NULL");
69         }
70         val prefixedPath = (schemaPath.getPath());
71         if (prefixedPath != null) {
72             return findNodeInSchemaContext(context,prefixedPath);
73         }
74         return null;
75     }
76
77     /**
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>.
81      * <br>
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.
85      * <br>
86      * In case that Schema Context or Module or Revision Aware XPath contains <code>null</code> references the method
87      * will throw IllegalArgumentException
88      * <br>
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>.
92      *
93      * @throws IllegalArgumentException
94      *
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.
100      */
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!");
105         }
106         if (module === null) {
107             throw new IllegalArgumentException("Module reference cannot be NULL!");
108         }
109         if (nonCondXPath === null) {
110             throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
111         }
112
113          val  strXPath = nonCondXPath.toString();
114         if (strXPath != null) {
115             if (strXPath.contains("[")) {
116                 throw new IllegalArgumentException("Revision Aware XPath cannot contains condition!");
117             }
118             if (nonCondXPath.isAbsolute()) {
119                  val qnamedPath = xpathToQNamePath(context, module, strXPath);
120                 if (qnamedPath != null) {
121                     return findNodeInSchemaContext(context,qnamedPath);
122                 }
123             }
124         }
125         return null;
126     }
127
128     /**
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>.
132      * <br>
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.
136      * <br>
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
139      * DataSchemaNode.
140      * <br>
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
143      * <br>
144      * If the Revision Aware XPath doesn't have flag <code>isAbsolute == false</code> the method will
145      * throw IllegalArgumentException.
146      * <br>
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>.
151      *
152      * @throws IllegalArgumentException
153      *
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>.
160      */
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!");
165         }
166         if (module === null) {
167             throw new IllegalArgumentException("Module reference cannot be NULL!");
168         }
169         if (actualSchemaNode === null) {
170             throw new IllegalArgumentException("Actual Schema Node reference cannot be NULL!");
171         }
172         if (relativeXPath === null) {
173             throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
174         }
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!");
178         }
179
180          val actualNodePath = actualSchemaNode.getPath();
181         if (actualNodePath != null) {
182              val qnamePath = resolveRelativeXPath(context, module, relativeXPath, actualSchemaNode);
183
184             if (qnamePath != null) {
185                 return findNodeInSchemaContext(context,qnamePath);
186             }
187         }
188         return null;
189     }
190
191     /**
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>.
194      * <br>
195      * If Schema Context or Schema Node contains <code>null</code> references the method will throw IllegalArgumentException
196      *
197      * @throws IllegalArgumentException
198      *
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>
203      */
204     public static def Module findParentModule( SchemaContext context,  SchemaNode schemaNode) {
205         if (context === null) {
206             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
207         }
208         if (schemaNode === null) {
209             throw new IllegalArgumentException("Schema Node cannot be NULL!");
210         }
211
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)");
216         }
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"
221                     + "of path.");
222         }
223         val qname = qnamedPath.get(qnamedPath.size() - 1);
224         return context.findModuleByNamespaceAndRevision(qname.namespace,qname.revision);
225     }
226
227     /**
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>
232      * <br>
233      * If Schema Context, Module or Queue of QNames refers to <code>null</code> values,
234      * the method will throws IllegalArgumentException
235      *
236      * @throws IllegalArgumentException
237      *
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>.
243      */
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!");
248         }
249         if (module === null) {
250             throw new IllegalArgumentException("Module reference cannot be NULL!");
251         }
252         if (module.getNamespace() === null) {
253             throw new IllegalArgumentException("Namespace for Module cannot contains NULL reference!");
254         }
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"
258                     + "of path.");
259         }
260
261         var DataNodeContainer nextNode = module;
262         val moduleNamespace = module.getNamespace();
263
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);
272                 }
273                 if(schemaNode === null && nextNode instanceof Module) {
274                     
275                 }
276                 val URI childNamespace = childNodeQName.getNamespace();
277                 val Date childRevision = childNodeQName.getRevision();
278                 
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;
286                         qnamedPath.poll();
287                         if (!qnamedPath.isEmpty()) {
288                             childNodeQName = qnamedPath.peek();
289                             nextNode = choice.getCaseNodeByName(childNodeQName);
290                             schemaNode = nextNode as DataSchemaNode;
291                         }
292                     } else {
293                         nextNode = null;
294                     }
295                 } else if (!childNamespace.equals(moduleNamespace)) {
296                     val Module nextModule = context.findModuleByNamespaceAndRevision(childNamespace,childRevision);
297                     schemaNode = findSchemaNodeForGivenPath(context, nextModule, qnamedPath);
298                     return schemaNode;
299                 }
300                 qnamedPath.poll();
301             }
302         }
303         return schemaNode;
304     }
305     
306     
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);
312     }
313     
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)
322         return null
323     }
324      
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)
331         }
332         return null
333     }
334     
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)
340         return null
341     }
342     
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)
348         return null
349     }
350     
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)
356         return null
357     }
358     
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)
364         return null
365     }
366     
367     private static dispatch def SchemaNode findNode(DataSchemaNode parent,List<QName> path){
368         if(path.empty) {
369             return parent
370         } else {
371             throw new IllegalArgumentException("Path nesting violation");
372         }
373     }
374     
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)
380         return null
381     }
382     
383      
384     public static def RpcDefinition getRpcByName(Module module, QName name) {
385         for(notification : module.rpcs) {
386             if(notification.QName == name) {
387                 return notification;
388             }
389         }
390         return null;
391     }
392     
393     
394     private static def nextLevel(List<QName> path){
395         return path.subList(1,path.size)
396     }
397     
398     public static def NotificationDefinition getNotificationByName(Module module, QName name) {
399         for(notification : module.notifications) {
400             if(notification.QName == name) {
401                 return notification;
402             }
403         }
404         return null;
405     }
406
407     /**
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.
410      * <br>
411      * If Schema Context, Parent Module or XPath string contains <code>null</code> values,
412      * the method will throws IllegalArgumentException
413      *
414      * @throws IllegalArgumentException
415      *
416      * @param context Schema Context
417      * @param parentModule Parent Module
418      * @param xpath XPath String
419      * @return
420      */
421     private static def xpathToQNamePath( SchemaContext context,  Module parentModule,
422              String xpath) {
423         if (context === null) {
424             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
425         }
426         if (parentModule === null) {
427             throw new IllegalArgumentException("Parent Module reference cannot be NULL!");
428         }
429         if (xpath === null) {
430             throw new IllegalArgumentException("XPath string reference cannot be NULL!");
431         }
432
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));
438             }
439         }
440         return path;
441     }
442
443     /**
444      * Transforms part of Prefixed Path as java String to QName.
445      * <br>
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
448      * constructed.
449      * <br>
450      * If the Prefixed Path Part does not contains prefix the Parent's Module namespace is taken for construction of
451      * QName.
452      * <br>
453      * If Schema Context, Parent Module or Prefixed Path Part refers to <code>null</code> the method will throw
454      * IllegalArgumentException
455      *
456      * @throws IllegalArgumentException
457      *
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.
462      */
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!");
467         }
468         if (parentModule === null) {
469             throw new IllegalArgumentException("Parent Module reference cannot be NULL!");
470         }
471         if (prefixedPathPart === null) {
472             throw new IllegalArgumentException("Prefixed Path Part cannot be NULL!");
473         }
474
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));
480             }
481         } else {
482             return new QName(parentModule.getNamespace(), parentModule.getRevision(), prefixedPathPart);
483         }
484         return null;
485     }
486
487     /**
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>.
493      * <br>
494      * If String prefix is the same as prefix of the specified Module the reference to this module is returned.
495      * <br>
496      * If Schema Context, Module or Prefix are referring to <code>null</code> the method will return
497      * IllegalArgumentException
498      *
499      * @throws IllegalArgumentException
500      *
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>
505      */
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!");
509         }
510         if (module === null) {
511             throw new IllegalArgumentException("Module reference cannot be NULL!");
512         }
513         if (prefix === null) {
514             throw new IllegalArgumentException("Prefix string cannot be NULL!");
515         }
516
517         if (prefix.equals(module.getPrefix())) {
518             return module;
519         }
520
521         val imports = module.getImports();
522         for ( ModuleImport mi : imports) {
523             if (prefix.equals(mi.getPrefix())) {
524                 return context.findModuleByName(mi.getModuleName(), mi.getRevision());
525             }
526         }
527         return null;
528     }
529
530     /**
531      * @throws IllegalArgumentException
532      *
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
537      * @return
538      */
539     private static def resolveRelativeXPath( SchemaContext context,  Module module,
540              RevisionAwareXPath relativeXPath,  SchemaNode leafrefParentNode) {
541
542         if (context === null) {
543             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
544         }
545         if (module === null) {
546             throw new IllegalArgumentException("Module reference cannot be NULL!");
547         }
548         if (relativeXPath === null) {
549             throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
550         }
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!");
554         }
555         if (leafrefParentNode.getPath() === null) {
556             throw new IllegalArgumentException("Schema Path reference for Leafref cannot be NULL!");
557         }
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;
566                 }
567                 val path = leafrefParentNode.getPath().getPath();
568                 if (path != null) {
569                     val int lenght = path.size() - colCount;
570                     absolutePath.addAll(path.subList(0,lenght));
571                     absolutePath.addAll(
572                         xpaths.subList(colCount,xpaths.length).map[stringPathPartToQName(context, module,it)]
573                     )
574                 }
575             }
576         }
577         return absolutePath;
578     }
579 }