Refactored base yang-java types.
[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     static void correctNodePath(final SchemaNodeBuilder node, final SchemaPath parentSchemaPath) {
252         // set correct path
253         List<QName> targetNodePath = new ArrayList<QName>(parentSchemaPath.getPath());
254         targetNodePath.add(node.getQName());
255         node.setPath(new SchemaPath(targetNodePath, true));
256
257         // set correct path for all child nodes
258         if (node instanceof DataNodeContainerBuilder) {
259             DataNodeContainerBuilder dataNodeContainer = (DataNodeContainerBuilder) node;
260             for (DataSchemaNodeBuilder child : dataNodeContainer.getChildNodeBuilders()) {
261                 correctNodePath(child, node.getPath());
262             }
263         }
264
265         // set correct path for all cases
266         if (node instanceof ChoiceBuilder) {
267             ChoiceBuilder choiceBuilder = (ChoiceBuilder) node;
268             for (ChoiceCaseBuilder choiceCaseBuilder : choiceBuilder.getCases()) {
269                 correctNodePath(choiceCaseBuilder, node.getPath());
270             }
271         }
272     }
273
274     /**
275      * Find augment target node and perform augmentation.
276      *
277      * @param augment
278      * @param firstNodeParent
279      *            parent of first node in path
280      * @param path
281      *            path to augment target
282      * @return true if augment process succeed, false otherwise
283      */
284     public static boolean processAugmentation(final AugmentationSchemaBuilder augment, final Builder firstNodeParent,
285             final List<QName> path) {
286         // traverse augment target path and try to reach target node
287         String currentName = null;
288         Builder currentParent = firstNodeParent;
289
290         for (int i = 0; i < path.size(); i++) {
291             QName qname = path.get(i);
292
293             currentName = qname.getLocalName();
294             if (currentParent instanceof DataNodeContainerBuilder) {
295                 DataSchemaNodeBuilder nodeFound = ((DataNodeContainerBuilder) currentParent)
296                         .getDataChildByName(currentName);
297                 // if not found as regular child, search in uses
298                 if (nodeFound == null) {
299                     boolean found = false;
300                     for (UsesNodeBuilder unb : ((DataNodeContainerBuilder) currentParent).getUsesNodes()) {
301                         DataSchemaNodeBuilder result = findNodeInUses(currentName, unb);
302                         if (result != null) {
303                             currentParent = result;
304                             found = true;
305                             break;
306                         }
307                     }
308                     // if not found even in uses nodes, return false
309                     if (!found) {
310                         return false;
311                     }
312                 } else {
313                     currentParent = nodeFound;
314                 }
315             } else if (currentParent instanceof ChoiceBuilder) {
316                 currentParent = ((ChoiceBuilder) currentParent).getCaseNodeByName(currentName);
317             } else {
318                 throw new YangParseException(augment.getModuleName(), augment.getLine(),
319                         "Error in augment parsing: failed to find node " + currentName);
320             }
321
322             // if node in path not found, return false
323             if (currentParent == null) {
324                 return false;
325             }
326         }
327         if (!(currentParent instanceof DataSchemaNodeBuilder)) {
328             throw new YangParseException(
329                     augment.getModuleName(),
330                     augment.getLine(),
331                     "Error in augment parsing: The target node MUST be either a container, list, choice, case, input, output, or notification node.");
332         }
333
334         if (currentParent instanceof ChoiceBuilder) {
335             fillAugmentTarget(augment, (ChoiceBuilder) currentParent);
336         } else {
337             fillAugmentTarget(augment, (DataNodeContainerBuilder) currentParent);
338         }
339         ((AugmentationTargetBuilder) currentParent).addAugmentation(augment);
340         SchemaPath oldPath = ((DataSchemaNodeBuilder) currentParent).getPath();
341         augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
342         augment.setResolved(true);
343
344         return true;
345     }
346
347     private static DataSchemaNodeBuilder findNodeInUses(String localName, UsesNodeBuilder uses) {
348         GroupingBuilder target = uses.getGroupingBuilder();
349         for (DataSchemaNodeBuilder child : target.getChildNodeBuilders()) {
350             if (child.getQName().getLocalName().equals(localName)) {
351                 return child;
352             }
353         }
354         for (UsesNodeBuilder usesNode : target.getUsesNodes()) {
355             DataSchemaNodeBuilder result = findNodeInUses(localName, usesNode);
356             if (result != null) {
357                 return result;
358             }
359         }
360         return null;
361     }
362
363     /**
364      * Find augment target node in given context and perform augmentation.
365      *
366      * @param augment
367      * @param path
368      *            path to augment target
369      * @param module
370      *            current module
371      * @param prefix
372      *            current prefix of target module
373      * @param context
374      *            SchemaContext containing already resolved modules
375      * @return true if augment process succeed, false otherwise
376      */
377     public static boolean processAugmentationOnContext(final AugmentationSchemaBuilder augment, final List<QName> path,
378             final ModuleBuilder module, final String prefix, final SchemaContext context) {
379         final int line = augment.getLine();
380         final Module dependentModule = findModuleFromContext(context, module, prefix, line);
381         if (dependentModule == null) {
382             throw new YangParseException(module.getName(), line,
383                     "Error in augment parsing: failed to find module with prefix " + prefix + ".");
384         }
385
386         String currentName = path.get(0).getLocalName();
387         SchemaNode currentParent = dependentModule.getDataChildByName(currentName);
388         if (currentParent == null) {
389             Set<NotificationDefinition> notifications = dependentModule.getNotifications();
390             for (NotificationDefinition ntf : notifications) {
391                 if (ntf.getQName().getLocalName().equals(currentName)) {
392                     currentParent = ntf;
393                     break;
394                 }
395             }
396         }
397         if (currentParent == null) {
398             throw new YangParseException(module.getName(), line, "Error in augment parsing: failed to find node "
399                     + currentName + ".");
400         }
401
402         for (int i = 1; i < path.size(); i++) {
403             currentName = path.get(i).getLocalName();
404             if (currentParent instanceof DataNodeContainer) {
405                 currentParent = ((DataNodeContainer) currentParent).getDataChildByName(currentName);
406             } else if (currentParent instanceof ChoiceNode) {
407                 currentParent = ((ChoiceNode) currentParent).getCaseNodeByName(currentName);
408             } else {
409                 throw new YangParseException(augment.getModuleName(), line,
410                         "Error in augment parsing: failed to find node " + currentName);
411             }
412             // if node in path not found, return false
413             if (currentParent == null) {
414                 throw new YangParseException(module.getName(), line, "Error in augment parsing: failed to find node "
415                         + currentName + ".");
416             }
417         }
418
419         if (currentParent instanceof ContainerSchemaNodeImpl) {
420             // includes container, input and output statement
421             ContainerSchemaNodeImpl c = (ContainerSchemaNodeImpl) currentParent;
422             ContainerSchemaNodeBuilder cb = c.toBuilder();
423             fillAugmentTarget(augment, cb);
424             ((AugmentationTargetBuilder) cb).addAugmentation(augment);
425             SchemaPath oldPath = cb.getPath();
426             cb.rebuild();
427             augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
428             augment.setResolved(true);
429         } else if (currentParent instanceof ListSchemaNodeImpl) {
430             ListSchemaNodeImpl l = (ListSchemaNodeImpl) currentParent;
431             ListSchemaNodeBuilder lb = l.toBuilder();
432             fillAugmentTarget(augment, lb);
433             ((AugmentationTargetBuilder) lb).addAugmentation(augment);
434             SchemaPath oldPath = lb.getPath();
435             lb.rebuild();
436             augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
437             augment.setResolved(true);
438         } else if (currentParent instanceof ChoiceNodeImpl) {
439             ChoiceNodeImpl ch = (ChoiceNodeImpl) currentParent;
440             ChoiceBuilder chb = ch.toBuilder();
441             fillAugmentTarget(augment, chb);
442             ((AugmentationTargetBuilder) chb).addAugmentation(augment);
443             SchemaPath oldPath = chb.getPath();
444             chb.rebuild();
445             augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
446             augment.setResolved(true);
447         } else if (currentParent instanceof ChoiceCaseNodeImpl) {
448             ChoiceCaseNodeImpl chc = (ChoiceCaseNodeImpl) currentParent;
449             ChoiceCaseBuilder chcb = chc.toBuilder();
450             fillAugmentTarget(augment, chcb);
451             ((AugmentationTargetBuilder) chcb).addAugmentation(augment);
452             SchemaPath oldPath = chcb.getPath();
453             chcb.rebuild();
454             augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
455             augment.setResolved(true);
456         } else if (currentParent instanceof NotificationDefinitionImpl) {
457             NotificationDefinitionImpl nd = (NotificationDefinitionImpl) currentParent;
458             NotificationBuilder nb = nd.toBuilder();
459             fillAugmentTarget(augment, nb);
460             ((AugmentationTargetBuilder) nb).addAugmentation(augment);
461             SchemaPath oldPath = nb.getPath();
462             nb.rebuild();
463             augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
464             augment.setResolved(true);
465         } else {
466             throw new YangParseException(module.getName(), line, "Target of type " + currentParent.getClass()
467                     + " cannot be augmented.");
468         }
469
470         return true;
471     }
472
473     public static QName findFullQName(final Map<String, TreeMap<Date, ModuleBuilder>> modules,
474             final ModuleBuilder module, final IdentityrefTypeBuilder idref) {
475         QName result = null;
476         String baseString = idref.getBaseString();
477         if (baseString.contains(":")) {
478             String[] splittedBase = baseString.split(":");
479             if (splittedBase.length > 2) {
480                 throw new YangParseException(module.getName(), idref.getLine(), "Failed to parse identityref base: "
481                         + baseString);
482             }
483             String prefix = splittedBase[0];
484             String name = splittedBase[1];
485             ModuleBuilder dependentModule = findDependentModuleBuilder(modules, module, prefix, idref.getLine());
486             result = new QName(dependentModule.getNamespace(), dependentModule.getRevision(), prefix, name);
487         } else {
488             result = new QName(module.getNamespace(), module.getRevision(), module.getPrefix(), baseString);
489         }
490         return result;
491     }
492
493     /**
494      * Get module in which this node is defined.
495      *
496      * @param node
497      * @return builder of module where this node is defined
498      */
499     public static ModuleBuilder getParentModule(Builder node) {
500         if(node instanceof ModuleBuilder) {
501             return (ModuleBuilder)node;
502         }
503
504         Builder parent = node.getParent();
505         while (!(parent instanceof ModuleBuilder)) {
506             parent = parent.getParent();
507         }
508         return (ModuleBuilder) parent;
509     }
510
511 }