Fixed bug in schema path of nested nodes defined in augment under uses.
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / util / ParserUtils.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.parser.util;
9
10 import java.util.ArrayList;
11 import java.util.Arrays;
12 import java.util.Date;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.TreeMap;
16
17 import org.opendaylight.yangtools.yang.common.QName;
18 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
19 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
20 import org.opendaylight.yangtools.yang.model.api.Module;
21 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
22 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
23 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
24 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
25 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
26 import org.opendaylight.yangtools.yang.parser.builder.api.AugmentationSchemaBuilder;
27 import org.opendaylight.yangtools.yang.parser.builder.api.AugmentationTargetBuilder;
28 import org.opendaylight.yangtools.yang.parser.builder.api.Builder;
29 import org.opendaylight.yangtools.yang.parser.builder.api.DataNodeContainerBuilder;
30 import org.opendaylight.yangtools.yang.parser.builder.api.DataSchemaNodeBuilder;
31 import org.opendaylight.yangtools.yang.parser.builder.api.SchemaNodeBuilder;
32 import org.opendaylight.yangtools.yang.parser.builder.api.UsesNodeBuilder;
33 import org.opendaylight.yangtools.yang.parser.builder.impl.ChoiceBuilder;
34 import org.opendaylight.yangtools.yang.parser.builder.impl.ChoiceBuilder.ChoiceNodeImpl;
35 import org.opendaylight.yangtools.yang.parser.builder.impl.ChoiceCaseBuilder;
36 import org.opendaylight.yangtools.yang.parser.builder.impl.ChoiceCaseBuilder.ChoiceCaseNodeImpl;
37 import org.opendaylight.yangtools.yang.parser.builder.impl.ContainerSchemaNodeBuilder.ContainerSchemaNodeImpl;
38 import org.opendaylight.yangtools.yang.parser.builder.impl.IdentityrefTypeBuilder;
39 import org.opendaylight.yangtools.yang.parser.builder.impl.ListSchemaNodeBuilder.ListSchemaNodeImpl;
40 import org.opendaylight.yangtools.yang.parser.builder.impl.ModuleBuilder;
41 import org.opendaylight.yangtools.yang.parser.builder.impl.NotificationBuilder.NotificationDefinitionImpl;
42 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget
43
44 public final class ParserUtils {
45
46     private new() {
47     }
48
49     /**
50      * Create new SchemaPath from given path and qname.
51      *
52      * @param schemaPath
53      * @param qname
54      * @return new SchemaPath from given path and qname
55      */
56     public static def SchemaPath createSchemaPath(SchemaPath schemaPath, QName... qname) {
57         val path = new ArrayList<QName>(schemaPath.getPath());
58         path.addAll(Arrays.asList(qname));
59         return new SchemaPath(path, schemaPath.isAbsolute());
60     }
61
62     /**
63      * Get module import referenced by given prefix.
64      *
65      * @param builder
66      *            module to search
67      * @param prefix
68      *            prefix associated with import
69      * @return ModuleImport based on given prefix
70      */
71     public static def ModuleImport getModuleImport(ModuleBuilder builder, String prefix) {
72         for (ModuleImport mi : builder.getModuleImports()) {
73             if (mi.getPrefix().equals(prefix)) {
74                 return mi;
75
76             }
77         }
78         return null;
79     }
80
81     /**
82      * Find dependent module based on given prefix
83      *
84      * @param modules
85      *            all available modules
86      * @param module
87      *            current module
88      * @param prefix
89      *            target module prefix
90      * @param line
91      *            current line in yang model
92      * @return module builder if found, null otherwise
93      */
94     public static def ModuleBuilder findModuleFromBuilders(Map<String, TreeMap<Date, ModuleBuilder>> modules,
95         ModuleBuilder module, String prefix, int line) {
96         var ModuleBuilder dependentModule = null;
97         var Date dependentModuleRevision = null;
98
99         if (prefix.equals(module.getPrefix())) {
100             dependentModule = module;
101         } else {
102             val ModuleImport dependentModuleImport = getModuleImport(module, prefix);
103             if (dependentModuleImport === null) {
104                 throw new YangParseException(module.getName(), line, "No import found with prefix '" + prefix + "'.");
105             }
106             val String dependentModuleName = dependentModuleImport.getModuleName();
107             dependentModuleRevision = dependentModuleImport.getRevision();
108
109             val TreeMap<Date, ModuleBuilder> moduleBuildersByRevision = modules.get(dependentModuleName);
110             if (moduleBuildersByRevision === null) {
111                 return null;
112             }
113             if (dependentModuleRevision === null) {
114                 dependentModule = moduleBuildersByRevision.lastEntry().getValue();
115             } else {
116                 dependentModule = moduleBuildersByRevision.get(dependentModuleRevision);
117             }
118         }
119         return dependentModule;
120     }
121
122     /**
123      * Find module from context based on prefix.
124      *
125      * @param context
126      *            schema context
127      * @param currentModule
128      *            current module
129      * @param prefix
130      *            current prefix used to reference dependent module
131      * @param line
132      *            current line in yang model
133      * @return module based on given prefix if found in context, null otherwise
134      */
135     public static def Module findModuleFromContext(SchemaContext context, ModuleBuilder currentModule,
136         String prefix, int line) {
137         if (context === null) {
138                 throw new YangParseException(currentModule.getName(), line, "Cannot find module with prefix '" + prefix + "'.");
139         }
140         val modulesByRevision = new TreeMap<Date, Module>();
141
142         val dependentModuleImport = ParserUtils.getModuleImport(currentModule, prefix);
143         if (dependentModuleImport === null) {
144             throw new YangParseException(currentModule.getName(), line, "No import found with prefix '" + prefix + "'.");
145         }
146         val dependentModuleName = dependentModuleImport.getModuleName();
147         val dependentModuleRevision = dependentModuleImport.getRevision();
148
149         for (Module contextModule : context.getModules()) {
150             if (contextModule.getName().equals(dependentModuleName)) {
151                 var revision = contextModule.getRevision();
152                 if (revision === null) {
153                     revision = new Date(0L);
154                 }
155                 modulesByRevision.put(revision, contextModule);
156             }
157         }
158
159         var Module result = null;
160         if (dependentModuleRevision === null) {
161             result = modulesByRevision.get(modulesByRevision.firstKey());
162         } else {
163             result = modulesByRevision.get(dependentModuleRevision);
164         }
165         return result;
166     }
167
168     /**
169      * Parse XPath string.
170      *
171      * @param xpathString
172      *            as String
173      * @return SchemaPath from given String
174      */
175     public static def SchemaPath parseXPathString(String xpathString) {
176         val absolute = xpathString.startsWith("/");
177         val String[] splittedPath = xpathString.split("/");
178         val path = new ArrayList<QName>();
179         var QName name;
180         for (String pathElement : splittedPath) {
181             if (pathElement.length() > 0) {
182                 val String[] splittedElement = pathElement.split(":");
183                 if (splittedElement.length == 1) {
184                     name = new QName(null, null, null, splittedElement.get(0));
185                 } else {
186                     name = new QName(null, null, splittedElement.get(0), splittedElement.get(1));
187                 }
188                 path.add(name);
189             }
190         }
191         return new SchemaPath(path, absolute);
192     }
193
194     /**
195      * Add all augment's child nodes to given target.
196      *
197      * @param augment
198      *            builder of augment statement
199      * @param target
200      *            augmentation target node
201      */
202     public static def void fillAugmentTarget(AugmentationSchemaBuilder augment, DataNodeContainerBuilder target) {
203         for (DataSchemaNodeBuilder child : augment.getChildNodeBuilders()) {
204             val childCopy = CopyUtils.copy(child, target, false);
205             setNodeAugmenting(childCopy, augment);
206             correctNodePath(child, target.getPath());
207             correctNodePath(childCopy, target.getPath());
208             try {
209                 target.addChildNode(childCopy);
210             } catch (YangParseException e) {
211
212                 // more descriptive message
213                 throw new YangParseException(augment.getModuleName(), augment.getLine(),
214                     "Failed to perform augmentation: " + e.getMessage());
215             }
216         }
217         for (UsesNodeBuilder usesNode : augment.getUsesNodes()) {
218             val copy = CopyUtils.copyUses(usesNode, target);
219             target.addUsesNode(copy);
220         }
221     }
222
223     private static def void setNodeAugmenting(DataSchemaNodeBuilder child, AugmentationSchemaBuilder augment) {
224         child.setAugmenting(true);
225         if (child instanceof DataNodeContainerBuilder) {
226             val DataNodeContainerBuilder dataNodeChild = child as DataNodeContainerBuilder;
227             for (inner : dataNodeChild.getChildNodeBuilders()) {
228                 setNodeAugmenting(inner, augment);
229             }
230             for (uses : dataNodeChild.getUsesNodes()) {
231                 uses.setParentAugment(augment);
232                 uses.setAugmenting(true);
233             }
234         }
235     }
236
237     /**
238      * Add all augment's child nodes to given target.
239      *
240      * @param augment
241      *            builder of augment statement
242      * @param target
243      *            augmentation target choice node
244      */
245     public static def void fillAugmentTarget(AugmentationSchemaBuilder augment, ChoiceBuilder target) {
246         for (DataSchemaNodeBuilder builder : augment.getChildNodeBuilders()) {
247             val childCopy = CopyUtils.copy(builder, target, false);
248             childCopy.setAugmenting(true);
249             correctNodePath(builder, target.getPath());
250             correctNodePath(childCopy, target.getPath());
251             target.addCase(childCopy);
252         }
253         for (UsesNodeBuilder usesNode : augment.getUsesNodes()) {
254             if (usesNode !== null) {
255                 throw new YangParseException(augment.getModuleName(), augment.getLine(),
256                     "Error in augment parsing: cannot augment uses to choice");
257             }
258         }
259     }
260
261     /**
262      * Create new schema path of node based on parent node schema path.
263      *
264      * @param node
265      *            node to correct
266      * @param parentSchemaPath
267      *            schema path of node parent
268      */
269     static def void correctNodePath(SchemaNodeBuilder node, SchemaPath parentSchemaPath) {
270
271         // set correct path
272         val targetNodePath = new ArrayList<QName>(parentSchemaPath.getPath());
273         targetNodePath.add(node.getQName());
274         node.setPath(new SchemaPath(targetNodePath, true));
275
276         // set correct path for all child nodes
277         if (node instanceof DataNodeContainerBuilder) {
278             val dataNodeContainer = node as DataNodeContainerBuilder;
279             for (DataSchemaNodeBuilder child : dataNodeContainer.getChildNodeBuilders()) {
280                 correctNodePath(child, node.getPath());
281             }
282         }
283
284         // set correct path for all cases
285         if (node instanceof ChoiceBuilder) {
286             val choiceBuilder = node as ChoiceBuilder;
287             for (ChoiceCaseBuilder choiceCaseBuilder : choiceBuilder.getCases()) {
288                 correctNodePath(choiceCaseBuilder, node.getPath());
289             }
290         }
291     }
292
293
294
295     private static def Builder findNode(Builder firstNodeParent, List<QName> path, String moduleName, int line) {
296         var currentName = "";
297         var currentParent = firstNodeParent;
298
299         val max = path.size();
300         var i = 0;
301         while(i < max) {
302             var qname = path.get(i);
303
304             currentName = qname.getLocalName();
305             if (currentParent instanceof DataNodeContainerBuilder) {
306                 var dataNodeContainerParent = currentParent as DataNodeContainerBuilder;
307                 var SchemaNodeBuilder nodeFound = dataNodeContainerParent.getDataChildByName(currentName);
308                 // if not found, search in notifications
309                 if (nodeFound == null && currentParent instanceof ModuleBuilder) {
310                         nodeFound = searchNotifications(currentParent as ModuleBuilder, currentName);
311                 }
312                 // if not found, search in uses
313                 if (nodeFound == null) {
314                     var found = searchUses(dataNodeContainerParent, currentName);
315                     if(found == null) {
316                         return null;
317                     } else {
318                         currentParent = found;
319                     }
320                 } else {
321                     currentParent = nodeFound;
322                 }
323             } else if (currentParent instanceof ChoiceBuilder) {
324                 val choiceParent = currentParent as ChoiceBuilder;
325                 currentParent = choiceParent.getCaseNodeByName(currentName);
326             } else {
327                 throw new YangParseException(moduleName, line,
328                         "Error in augment parsing: failed to find node " + currentName);
329             }
330
331             // if node in path not found, return null
332             if (currentParent == null) {
333                 return null;
334             }
335             i = i + 1; 
336         }
337         return currentParent;
338     }
339
340     private static def searchNotifications(ModuleBuilder parent, String name) {
341         for(notification : parent.notifications) {
342             if(notification.getQName().localName.equals(name)) {
343                 return notification;
344             }
345         }
346         return null;
347     }
348
349     private static def searchUses(DataNodeContainerBuilder dataNodeContainerParent, String name) {
350         var currentName = name;
351         for (unb : dataNodeContainerParent.usesNodes) {
352             val result = findNodeInUses(currentName, unb);
353             if (result != null) {
354                 var copy = CopyUtils.copy(result, unb.getParent(), true);
355                 unb.getTargetChildren().add(copy);
356                 return copy;
357             }
358         }
359         return null;
360     }
361     
362     public static def getRpc(ModuleBuilder module,String name) {
363         for(rpc : module.rpcs) {
364             if(name == rpc.QName.localName) {
365                 return rpc;
366             }
367         }
368         return null;
369     }
370     
371     public static def getNotification(ModuleBuilder module,String name) {
372         for(notification : module.notifications) {
373             if(name == notification.QName.localName) {
374                 return notification;
375             }
376         }
377     }
378     
379     private static def nextLevel(List<QName> path){
380         return path.subList(1,path.size)
381     }
382     
383     /**
384      * Find augment target node and perform augmentation.
385      *
386      * @param augment
387      * @param firstNodeParent
388      *            parent of first node in path
389      * @param path
390      *            path to augment target
391      * @return true if augmentation process succeed, false otherwise
392      */
393     public static def boolean processAugmentation(AugmentationSchemaBuilder augment, Builder firstNodeParent,
394         List<QName> path) {
395
396             // traverse augment target path and try to reach target node
397             val targetNode = findNode(firstNodeParent,path,augment.moduleName,augment.line);
398             if (targetNode === null) return false;
399             
400             if ((targetNode instanceof DataNodeContainerBuilder)) {
401                 val targetDataNodeContainer = targetNode as DataNodeContainerBuilder;
402                 augment.setTargetNodeSchemaPath(targetDataNodeContainer.getPath());
403                 fillAugmentTarget(augment, targetDataNodeContainer);
404             } else if (targetNode instanceof ChoiceBuilder) {
405                 val targetChoiceBuilder = targetNode as ChoiceBuilder;
406                 augment.setTargetNodeSchemaPath(targetChoiceBuilder.getPath());
407                 fillAugmentTarget(augment, targetChoiceBuilder);
408             } else {
409                 throw new YangParseException(augment.getModuleName(), augment.getLine(),
410                     "Error in augment parsing: The target node MUST be either a container, list, choice, case, input, output, or notification node.");
411             }
412             (targetNode as AugmentationTargetBuilder).addAugmentation(augment);
413             augment.setResolved(true);
414             return true;
415         }
416
417         /**
418      * Find node with given name in uses target.
419      *
420      * @param localName
421      *            name of node to find
422      * @param uses
423      *            uses node which target grouping should be searched
424      * @return node with given name if found, null otherwise
425      */
426     private static def DataSchemaNodeBuilder findNodeInUses(String localName, UsesNodeBuilder uses) {
427         for(child : uses.targetChildren) {
428             if (child.getQName().getLocalName().equals(localName)) {
429                 return child;
430             }
431         }
432
433         val target = uses.groupingBuilder;
434         for (child : target.childNodeBuilders) {
435             if (child.getQName().getLocalName().equals(localName)) {
436                 return child;
437             }
438         }
439         for (usesNode : target.usesNodes) {
440             val result = findNodeInUses(localName, usesNode);
441             if (result != null) {
442                 return result;
443             }
444         }
445         return null;
446     }
447
448         /**
449      * Find augment target node in given context and perform augmentation.
450      *
451      * @param augment
452      * @param path
453      *            path to augment target
454      * @param module
455      *            current module
456      * @param prefix
457      *            current prefix of target module
458      * @param context
459      *            SchemaContext containing already resolved modules
460      * @return true if augment process succeed, false otherwise
461      */
462         public static def boolean processAugmentationOnContext(AugmentationSchemaBuilder augment, List<QName> path,
463             ModuleBuilder module, String prefix, SchemaContext context) {
464             val int line = augment.getLine();
465             val Module dependentModule = findModuleFromContext(context, module, prefix, line);
466             if (dependentModule === null) {
467                 throw new YangParseException(module.getName(), line,
468                     "Error in augment parsing: failed to find module with prefix " + prefix + ".");
469             }
470
471             var currentName = path.get(0).getLocalName();
472             var SchemaNode currentParent = dependentModule.getDataChildByName(currentName);
473             if (currentParent === null) {
474                 val notifications = dependentModule.getNotifications();
475                 for (NotificationDefinition ntf : notifications) {
476                     if (ntf.getQName().getLocalName().equals(currentName)) {
477                         currentParent = ntf;
478                     }
479                 }
480             }
481             if (currentParent === null) {
482                 throw new YangParseException(module.getName(), line,
483                     "Error in augment parsing: failed to find node " + currentName + ".");
484             }
485
486             for (qname : path.nextLevel) {
487                 currentName = qname.getLocalName();
488                 if (currentParent instanceof DataNodeContainer) {
489                     currentParent = (currentParent as DataNodeContainer).getDataChildByName(currentName);
490                 } else if (currentParent instanceof ChoiceNode) {
491                     currentParent = (currentParent as ChoiceNode).getCaseNodeByName(currentName);
492                 } else {
493                     throw new YangParseException(augment.getModuleName(), line,
494                         "Error in augment parsing: failed to find node " + currentName);
495                 }
496
497                 // if node in path not found, return false
498                 if (currentParent === null) {
499                     throw new YangParseException(module.getName(), line,
500                         "Error in augment parsing: failed to find node " + currentName + ".");
501                 }
502             }
503
504             val oldPath = currentParent.path;
505
506             if (!(currentParent instanceof AugmentationTarget)) {
507                 throw new YangParseException(module.getName(), line,
508                     "Target of type " + currentParent.class + " cannot be augmented.");
509             }
510
511             switch (currentParent) {
512                 case (currentParent instanceof ContainerSchemaNodeImpl): {
513
514                     // includes container, input and output statement
515                     val c = currentParent as ContainerSchemaNodeImpl;
516                     val cb = c.toBuilder();
517                     fillAugmentTarget(augment, cb);
518                     (cb as AugmentationTargetBuilder ).addAugmentation(augment);
519                     cb.rebuild();
520                 }
521                 case (currentParent instanceof ListSchemaNodeImpl): {
522                     val l = currentParent as ListSchemaNodeImpl;
523                     val lb = l.toBuilder();
524                     fillAugmentTarget(augment, lb);
525                     (lb as AugmentationTargetBuilder ).addAugmentation(augment);
526                     lb.rebuild();
527                     augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
528                     augment.setResolved(true);
529                 }
530                 case (currentParent instanceof ChoiceNodeImpl): {
531                     val ch = currentParent as ChoiceNodeImpl;
532                     val chb = ch.toBuilder();
533                     fillAugmentTarget(augment, chb);
534                     (chb as AugmentationTargetBuilder ).addAugmentation(augment);
535                     chb.rebuild();
536                     augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
537                     augment.setResolved(true);
538                 }
539                 case (currentParent instanceof ChoiceCaseNodeImpl): {
540                     val chc = currentParent as ChoiceCaseNodeImpl;
541                     val chcb = chc.toBuilder();
542                     fillAugmentTarget(augment, chcb);
543                     (chcb as AugmentationTargetBuilder ).addAugmentation(augment);
544                     chcb.rebuild();
545                     augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
546                     augment.setResolved(true);
547                 }
548                 case (currentParent instanceof NotificationDefinitionImpl): {
549                     val nd = currentParent as NotificationDefinitionImpl;
550                     val nb = nd.toBuilder();
551                     fillAugmentTarget(augment, nb);
552                     (nb as AugmentationTargetBuilder ).addAugmentation(augment);
553                     nb.rebuild();
554                     augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
555                     augment.setResolved(true);
556                 }
557             }
558             augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
559             augment.setResolved(true);
560             return true;
561         }
562
563         public static def QName findFullQName(Map<String, TreeMap<Date, ModuleBuilder>> modules,
564             ModuleBuilder module, IdentityrefTypeBuilder idref) {
565             var QName result = null;
566             val String baseString = idref.getBaseString();
567             if (baseString.contains(":")) {
568                 val String[] splittedBase = baseString.split(":");
569                 if (splittedBase.length > 2) {
570                     throw new YangParseException(module.getName(), idref.getLine(),
571                         "Failed to parse identityref base: " + baseString);
572                 }
573                 val prefix = splittedBase.get(0);
574                 val name = splittedBase.get(1);
575                 val dependentModule = findModuleFromBuilders(modules, module, prefix, idref.getLine());
576                 result = new QName(dependentModule.getNamespace(), dependentModule.getRevision(), prefix, name);
577             } else {
578                 result = new QName(module.getNamespace(), module.getRevision(), module.getPrefix(), baseString);
579             }
580             return result;
581         }
582
583         /**
584      * Get module in which this node is defined.
585      *
586      * @param node
587      * @return builder of module where this node is defined
588      */
589         public static def ModuleBuilder getParentModule(Builder node) {
590             if (node instanceof ModuleBuilder) {
591                 return node as ModuleBuilder;
592             }
593             var parent = node.getParent();
594             while (!(parent instanceof ModuleBuilder)) {
595                 parent = parent.getParent();
596             }
597             return parent as ModuleBuilder;
598         }
599     }
600