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