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