Added support for parsing submodules & added dependency utility parser
[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.Set;
16 import java.util.TreeMap;
17
18 import org.opendaylight.yangtools.yang.common.QName;
19 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
20 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
21 import org.opendaylight.yangtools.yang.model.api.Module;
22 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
23 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
24 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
25 import org.opendaylight.yangtools.yang.parser.builder.api.AugmentationSchemaBuilder;
26 import org.opendaylight.yangtools.yang.parser.builder.api.AugmentationTargetBuilder;
27 import org.opendaylight.yangtools.yang.parser.builder.api.Builder;
28 import org.opendaylight.yangtools.yang.parser.builder.api.DataNodeContainerBuilder;
29 import org.opendaylight.yangtools.yang.parser.builder.api.DataSchemaNodeBuilder;
30 import org.opendaylight.yangtools.yang.parser.builder.api.SchemaNodeBuilder;
31 import org.opendaylight.yangtools.yang.parser.builder.api.UsesNodeBuilder;
32 import org.opendaylight.yangtools.yang.parser.builder.impl.ChoiceBuilder;
33 import org.opendaylight.yangtools.yang.parser.builder.impl.ChoiceCaseBuilder;
34 import org.opendaylight.yangtools.yang.parser.builder.impl.IdentitySchemaNodeBuilder;
35 import org.opendaylight.yangtools.yang.parser.builder.impl.ModuleBuilder;
36 import org.opendaylight.yangtools.yang.parser.builder.impl.RpcDefinitionBuilder
37 import org.opendaylight.yangtools.yang.parser.builder.api.GroupingMember
38 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode
39 import java.util.HashSet
40 import java.net.URI
41 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode
42 import org.opendaylight.yangtools.yang.parser.builder.impl.AnyXmlBuilder
43 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode
44 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
45 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode
46 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode
47 import org.opendaylight.yangtools.yang.parser.builder.impl.ContainerSchemaNodeBuilder
48 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode
49 import org.opendaylight.yangtools.yang.parser.builder.impl.ListSchemaNodeBuilder
50 import org.opendaylight.yangtools.yang.parser.builder.impl.LeafListSchemaNodeBuilder
51 import org.opendaylight.yangtools.yang.parser.builder.impl.LeafSchemaNodeBuilder
52 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition
53 import org.opendaylight.yangtools.yang.parser.builder.api.GroupingBuilder
54 import org.opendaylight.yangtools.yang.parser.builder.impl.GroupingBuilderImpl
55 import org.opendaylight.yangtools.yang.model.api.TypeDefinition
56 import org.opendaylight.yangtools.yang.parser.builder.impl.TypeDefinitionBuilderImpl
57 import org.opendaylight.yangtools.yang.model.util.ExtendedType
58 import org.opendaylight.yangtools.yang.parser.builder.api.TypeDefinitionBuilder
59 import org.opendaylight.yangtools.yang.parser.builder.impl.UnknownSchemaNodeBuilder
60 import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode
61 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer
62 import com.google.common.base.Preconditions
63
64 public final class ParserUtils {
65
66     private new() {
67     }
68
69     /**
70      * Create new SchemaPath from given path and qname.
71      *
72      * @param schemaPath
73      * @param qname
74      * @return new SchemaPath from given path and qname
75      */
76     public static def SchemaPath createSchemaPath(SchemaPath schemaPath, QName... qname) {
77         val path = new ArrayList<QName>(schemaPath.getPath());
78         path.addAll(Arrays.asList(qname));
79         return new SchemaPath(path, schemaPath.isAbsolute());
80     }
81
82     /**
83      * Get module import referenced by given prefix.
84      *
85      * @param builder
86      *            module to search
87      * @param prefix
88      *            prefix associated with import
89      * @return ModuleImport based on given prefix
90      */
91     public static def ModuleImport getModuleImport(ModuleBuilder builder, String prefix) {
92         for (ModuleImport mi : builder.getModuleImports()) {
93             if (mi.getPrefix().equals(prefix)) {
94                 return mi;
95
96             }
97         }
98         return null;
99     }
100
101     /**
102      * Find dependent module based on given prefix
103      *
104      * @param modules
105      *            all available modules
106      * @param module
107      *            current module
108      * @param prefix
109      *            target module prefix
110      * @param line
111      *            current line in yang model
112      * @return module builder if found, null otherwise
113      */
114     public static def ModuleBuilder findModuleFromBuilders(Map<String, TreeMap<Date, ModuleBuilder>> modules,
115         ModuleBuilder module, String prefix, int line) {
116         var ModuleBuilder dependentModule = null;
117         var Date dependentModuleRevision = null;
118
119         if(prefix == null) {
120             dependentModule = module;
121         } else if (prefix.equals(module.getPrefix())) {
122             dependentModule = module;
123         } else {
124             val ModuleImport dependentModuleImport = getModuleImport(module, prefix);
125             if (dependentModuleImport === null) {
126                 throw new YangParseException(module.getName(), line, "No import found with prefix '" + prefix + "'.");
127             }
128             val String dependentModuleName = dependentModuleImport.getModuleName();
129             dependentModuleRevision = dependentModuleImport.getRevision();
130
131             val TreeMap<Date, ModuleBuilder> moduleBuildersByRevision = modules.get(dependentModuleName);
132             if (moduleBuildersByRevision === null) {
133                 return null;
134             }
135             if (dependentModuleRevision === null) {
136                 dependentModule = moduleBuildersByRevision.lastEntry().getValue();
137             } else {
138                 dependentModule = moduleBuildersByRevision.get(dependentModuleRevision);
139             }
140         }
141         return dependentModule;
142     }
143
144     /**
145      * Find module from context based on prefix.
146      *
147      * @param context
148      *            schema context
149      * @param currentModule
150      *            current module
151      * @param prefix
152      *            current prefix used to reference dependent module
153      * @param line
154      *            current line in yang model
155      * @return module based on given prefix if found in context, null otherwise
156      */
157     public static def Module findModuleFromContext(SchemaContext context, ModuleBuilder currentModule,
158         String prefix, int line) {
159         if (context === null) {
160             throw new YangParseException(currentModule.getName(), line,
161                 "Cannot find module with prefix '" + prefix + "'.");
162         }
163         val modulesByRevision = new TreeMap<Date, Module>();
164
165         val dependentModuleImport = ParserUtils.getModuleImport(currentModule, prefix);
166         if (dependentModuleImport === null) {
167             throw new YangParseException(currentModule.getName(), line, "No import found with prefix '" + prefix + "'.");
168         }
169         val dependentModuleName = dependentModuleImport.getModuleName();
170         val dependentModuleRevision = dependentModuleImport.getRevision();
171
172         for (Module contextModule : context.getModules()) {
173             if (contextModule.getName().equals(dependentModuleName)) {
174                 var revision = contextModule.getRevision();
175                 if (revision === null) {
176                     revision = new Date(0L);
177                 }
178                 modulesByRevision.put(revision, contextModule);
179             }
180         }
181
182         var Module result = null;
183         if (dependentModuleRevision === null) {
184             result = modulesByRevision.get(modulesByRevision.firstKey());
185         } else {
186             result = modulesByRevision.get(dependentModuleRevision);
187         }
188         return result;
189     }
190
191     /**
192      * Parse XPath string.
193      *
194      * @param xpathString
195      *            as String
196      * @return SchemaPath from given String
197      */
198     public static def SchemaPath parseXPathString(String xpathString) {
199         val absolute = xpathString.startsWith("/");
200         val String[] splittedPath = xpathString.split("/");
201         val path = new ArrayList<QName>();
202         var QName name;
203         for (String pathElement : splittedPath) {
204             if (pathElement.length() > 0) {
205                 val String[] splittedElement = pathElement.split(":");
206                 if (splittedElement.length == 1) {
207                     name = new QName(null, null, null, splittedElement.get(0));
208                 } else {
209                     name = new QName(null, null, splittedElement.get(0), splittedElement.get(1));
210                 }
211                 path.add(name);
212             }
213         }
214         return new SchemaPath(path, absolute);
215     }
216
217     public static def dispatch fillAugmentTarget(AugmentationSchemaBuilder augment, Builder target) {
218     }
219
220     /**
221      * Add all augment's child nodes to given target.
222      *
223      * @param augment
224      *            builder of augment statement
225      * @param target
226      *            augmentation target node
227      */
228     public static def dispatch fillAugmentTarget(AugmentationSchemaBuilder augment, DataNodeContainerBuilder target) {
229         for (DataSchemaNodeBuilder child : augment.getChildNodeBuilders()) {
230             val childCopy = CopyUtils.copy(child, target, false);
231             if (augment.parent instanceof UsesNodeBuilder) {
232                 setNodeAddedByUses(childCopy);
233             }
234             setNodeAugmenting(childCopy);
235             try {
236                 target.addChildNode(childCopy);
237             } catch (YangParseException e) {
238
239                 // more descriptive message
240                 throw new YangParseException(augment.getModuleName(), augment.getLine(),
241                     "Failed to perform augmentation: " + e.getMessage());
242             }
243         }
244     }
245
246     /**
247      * Add all augment's child nodes to given target.
248      *
249      * @param augment
250      *            builder of augment statement
251      * @param target
252      *            augmentation target choice node
253      */
254     public static def dispatch fillAugmentTarget(AugmentationSchemaBuilder augment, ChoiceBuilder target) {
255         for (DataSchemaNodeBuilder builder : augment.getChildNodeBuilders()) {
256             val childCopy = CopyUtils.copy(builder, target, false);
257             if (augment.parent instanceof UsesNodeBuilder) {
258                 setNodeAddedByUses(childCopy);
259             }
260             setNodeAugmenting(childCopy)
261             target.addCase(childCopy);
262         }
263         for (UsesNodeBuilder usesNode : augment.getUsesNodeBuilders()) {
264             if (usesNode !== null) {
265                 throw new YangParseException(augment.getModuleName(), augment.getLine(),
266                     "Error in augment parsing: cannot augment choice with nodes from grouping");
267             }
268         }
269     }
270
271     /**
272      * Set augmenting flag to true for node and all its child nodes.
273      */
274     private static def void setNodeAugmenting(DataSchemaNodeBuilder child) {
275         child.setAugmenting(true);
276         if (child instanceof DataNodeContainerBuilder) {
277             val DataNodeContainerBuilder dataNodeChild = child as DataNodeContainerBuilder;
278             for (inner : dataNodeChild.getChildNodeBuilders()) {
279                 setNodeAugmenting(inner);
280             }
281         } else if (child instanceof ChoiceBuilder) {
282             val ChoiceBuilder choiceChild = child as ChoiceBuilder;
283             for (inner : choiceChild.cases) {
284                 setNodeAugmenting(inner);
285             }
286         }
287     }
288
289     /**
290      * Set addedByUses flag to true for node and all its child nodes. 
291      */
292     public static def void setNodeAddedByUses(GroupingMember child) {
293         child.setAddedByUses(true);
294         if (child instanceof DataNodeContainerBuilder) {
295             val DataNodeContainerBuilder dataNodeChild = child as DataNodeContainerBuilder;
296             for (inner : dataNodeChild.getChildNodeBuilders()) {
297                 setNodeAddedByUses(inner);
298             }
299         } else if (child instanceof ChoiceBuilder) {
300             val ChoiceBuilder choiceChild = child as ChoiceBuilder;
301             for (inner : choiceChild.cases) {
302                 setNodeAddedByUses(inner);
303             }
304         }
305     }
306
307     public static def DataSchemaNodeBuilder findSchemaNode(List<QName> path, SchemaNodeBuilder parentNode) {
308         var DataSchemaNodeBuilder node
309         var SchemaNodeBuilder parent = parentNode
310         var int i = 0;
311         while (i < path.size) {
312             val String name = path.get(i).localName
313             if (parent instanceof DataNodeContainerBuilder) {
314                 node = (parent as DataNodeContainerBuilder).getDataChildByName(name)
315             } else if (parent instanceof ChoiceBuilder) {
316                 node = (parent as ChoiceBuilder).getCaseNodeByName(name)
317             } else if (parent instanceof RpcDefinitionBuilder) {
318                 if ("input".equals(name)) {
319                     node = (parent as RpcDefinitionBuilder).input
320                 } else if ("output".equals(name)) {
321                     node = (parent as RpcDefinitionBuilder).output
322                 } else {
323                     return null
324                 }
325             } else {
326                 return null
327             }
328
329             if (i < path.size - 1) {
330                 parent = node
331             }
332             i = i + 1
333         }
334
335         return node
336     }
337
338     public static def SchemaNodeBuilder findSchemaNodeInModule(List<QName> pathToNode, ModuleBuilder module) {
339         val List<QName> path = new ArrayList(pathToNode)
340         val QName first = path.remove(0)
341
342         var SchemaNodeBuilder node = module.getDataChildByName(first.localName)
343         if (node == null) {
344             val notifications = module.getAddedNotifications
345             for (notification : notifications) {
346                 if (notification.QName.localName.equals(first.localName)) {
347                     node = notification
348                 }
349             }
350         }
351         if (node == null) {
352             val rpcs = module.getAddedRpcs
353             for (rpc : rpcs) {
354                 if (rpc.QName.localName.equals(first.localName)) {
355                     node = rpc
356                 }
357             }
358         }
359         if (node == null) {
360             return null;
361         }
362
363         if (!path.empty) {
364             node = findSchemaNode(path, node)
365         }
366
367         return node
368     }
369
370     /**
371      * Find augment target node and perform augmentation.
372      *
373      * @param augment
374      * @param firstNodeParent
375      *            parent of first node in path
376      * @param path
377      *            path to augment target
378      * @return true if augmentation process succeed, false otherwise
379      */
380     public static def boolean processAugmentation(AugmentationSchemaBuilder augment, ModuleBuilder firstNodeParent) {
381         val path = augment.targetPath.path
382         var Builder targetNode = findSchemaNodeInModule(path, firstNodeParent as ModuleBuilder)
383         if(targetNode === null) return false;
384
385         if ((targetNode instanceof DataNodeContainerBuilder)) {
386             val targetDataNodeContainer = targetNode as DataNodeContainerBuilder;
387             augment.setTargetNodeSchemaPath(targetDataNodeContainer.getPath());
388         } else if (targetNode instanceof ChoiceBuilder) {
389             val targetChoiceBuilder = targetNode as ChoiceBuilder;
390             augment.setTargetNodeSchemaPath(targetChoiceBuilder.getPath());
391         } else {
392             throw new YangParseException(augment.getModuleName(), augment.getLine(),
393                 "Error in augment parsing: The target node MUST be either a container, list, choice, case, input, output, or notification node.");
394         }
395         fillAugmentTarget(augment, targetNode);
396         (targetNode as AugmentationTargetBuilder).addAugmentation(augment);
397         augment.setResolved(true);
398         return true;
399     }
400
401     public static def IdentitySchemaNodeBuilder findBaseIdentity(Map<String, TreeMap<Date, ModuleBuilder>> modules,
402         ModuleBuilder module, String baseString, int line) {
403         var IdentitySchemaNodeBuilder result = null;
404         if (baseString.contains(":")) {
405             val String[] splittedBase = baseString.split(":");
406             if (splittedBase.length > 2) {
407                 throw new YangParseException(module.getName(), line,
408                     "Failed to parse identityref base: " + baseString);
409             }
410             val prefix = splittedBase.get(0);
411             val name = splittedBase.get(1);
412             val dependentModule = findModuleFromBuilders(modules, module, prefix, line);
413             if (dependentModule !== null) {
414                 result = findIdentity(dependentModule.getAddedIdentities, name);
415             }
416         } else {
417             result = findIdentity(module.getAddedIdentities, baseString);
418         }
419         return result;
420     }
421
422     public static def IdentitySchemaNode findBaseIdentityFromContext(Map<String, TreeMap<Date, ModuleBuilder>> modules,
423         ModuleBuilder module, String baseString, int line, SchemaContext context) {
424         var IdentitySchemaNode result = null;
425
426         val String[] splittedBase = baseString.split(":");
427         if (splittedBase.length > 2) {
428             throw new YangParseException(module.getName(), line, "Failed to parse identityref base: " + baseString);
429         }
430         val prefix = splittedBase.get(0);
431         val name = splittedBase.get(1);
432         val dependentModule = findModuleFromContext(context, module, prefix, line);
433         result = findIdentityNode(dependentModule.identities, name);
434
435         if (result == null) {
436             throw new YangParseException(module.name, line, "Failed to find base identity");
437         }
438         return result;
439     }
440
441     private static def IdentitySchemaNodeBuilder findIdentity(Set<IdentitySchemaNodeBuilder> identities, String name) {
442         for (identity : identities) {
443             if (identity.QName.localName.equals(name)) {
444                 return identity;
445             }
446         }
447         return null;
448     }
449
450     private static def IdentitySchemaNode findIdentityNode(Set<IdentitySchemaNode> identities, String name) {
451         for (identity : identities) {
452             if (identity.QName.localName.equals(name)) {
453                 return identity;
454             }
455         }
456         return null;
457     }
458
459     /**
460      * Get module in which this node is defined.
461      *
462      * @param node
463      * @return builder of module where this node is defined
464      */
465     public static def ModuleBuilder getParentModule(Builder node) {
466         if (node instanceof ModuleBuilder) {
467             return node as ModuleBuilder;
468         }
469         var parent = node.getParent();
470         while (!(parent instanceof ModuleBuilder)) {
471             parent = parent.getParent();
472         }
473         Preconditions.checkState(parent instanceof ModuleBuilder)
474         var parentModule = parent as ModuleBuilder
475         if(parentModule.submodule) {
476            parentModule = parentModule.parent; 
477         }
478         return parentModule;
479     }
480
481     public static def Set<DataSchemaNodeBuilder> wrapChildNodes(String moduleName, int line, Set<DataSchemaNode> nodes,
482         SchemaPath parentPath, URI ns, Date rev, String pref) {
483         val Set<DataSchemaNodeBuilder> result = new HashSet()
484
485         for (DataSchemaNode node : nodes) {
486             val qname = new QName(ns, rev, pref, node.QName.localName)
487             val DataSchemaNodeBuilder wrapped = wrapChildNode(moduleName, line, node, parentPath, qname)
488             result.add(wrapped)
489         }
490         return result
491     }
492
493     public static def DataSchemaNodeBuilder wrapChildNode(String moduleName, int line, DataSchemaNode node,
494         SchemaPath parentPath, QName qname) {
495         val List<QName> path = new ArrayList(parentPath.getPath())
496         path.add(qname)
497         val SchemaPath schemaPath = new SchemaPath(path, parentPath.isAbsolute())
498
499         if (node instanceof AnyXmlSchemaNode) {
500             return new AnyXmlBuilder(moduleName, line, qname, schemaPath, (node as AnyXmlSchemaNode));
501         } else if (node instanceof ChoiceNode) {
502             return new ChoiceBuilder(moduleName, line, qname, schemaPath, (node as ChoiceNode));
503         } else if (node instanceof ContainerSchemaNode) {
504             return new ContainerSchemaNodeBuilder(moduleName, line, qname, schemaPath, (node as ContainerSchemaNode));
505         } else if (node instanceof LeafSchemaNode) {
506             return new LeafSchemaNodeBuilder(moduleName, line, qname, schemaPath, (node as LeafSchemaNode));
507         } else if (node instanceof LeafListSchemaNode) {
508             return new LeafListSchemaNodeBuilder(moduleName, line, qname, schemaPath, (node as LeafListSchemaNode));
509         } else if (node instanceof ListSchemaNode) {
510             return new ListSchemaNodeBuilder(moduleName, line, qname, schemaPath, (node as ListSchemaNode));
511         } else if (node instanceof ChoiceCaseNode) {
512             return new ChoiceCaseBuilder(moduleName, line, qname, schemaPath, (node as ChoiceCaseNode));
513         } else {
514             throw new YangParseException(moduleName, line,
515                 "Failed to copy node: Unknown type of DataSchemaNode: " + node)
516         }
517     }
518
519     public static def Set<GroupingBuilder> wrapGroupings(String moduleName, int line, Set<GroupingDefinition> nodes,
520         SchemaPath parentPath, URI ns, Date rev, String pref) {
521         val Set<GroupingBuilder> result = new HashSet()
522         for (GroupingDefinition node : nodes) {
523             val qname = new QName(ns, rev, pref, node.QName.localName)
524             val List<QName> path = new ArrayList(parentPath.getPath())
525             path.add(qname)
526             val SchemaPath schemaPath = new SchemaPath(path, parentPath.isAbsolute())
527             result.add(new GroupingBuilderImpl(moduleName, line, qname, schemaPath, node))
528         }
529         return result
530     }
531
532     public static def Set<TypeDefinitionBuilder> wrapTypedefs(String moduleName, int line, DataNodeContainer dataNode,
533         SchemaPath parentPath, URI ns, Date rev, String pref) {
534         val Set<TypeDefinition<?>> nodes = dataNode.typeDefinitions
535         val Set<TypeDefinitionBuilder> result = new HashSet()
536         for (TypeDefinition<?> node : nodes) {
537             val qname = new QName(ns, rev, pref, node.QName.localName)
538             val List<QName> path = new ArrayList(parentPath.getPath())
539             path.add(qname)
540             val SchemaPath schemaPath = new SchemaPath(path, parentPath.isAbsolute())
541             result.add(new TypeDefinitionBuilderImpl(moduleName, line, qname, schemaPath, (node as ExtendedType)))
542         }
543         return result
544     }
545
546     public static def List<UnknownSchemaNodeBuilder> wrapUnknownNodes(String moduleName, int line,
547         List<UnknownSchemaNode> nodes, SchemaPath parentPath, URI ns, Date rev, String pref) {
548         val List<UnknownSchemaNodeBuilder> result = new ArrayList()
549         for (UnknownSchemaNode node : nodes) {
550             val qname = new QName(ns, rev, pref, node.QName.localName)
551             val List<QName> path = new ArrayList(parentPath.getPath())
552             path.add(qname)
553             val SchemaPath schemaPath = new SchemaPath(path, parentPath.isAbsolute())
554             result.add(new UnknownSchemaNodeBuilder(moduleName, line, qname, schemaPath, (node as UnknownSchemaNode)))
555         }
556         return result
557     }
558
559 }