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