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