2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.yang.parser.util;
10 import java.util.ArrayList;
11 import java.util.Arrays;
12 import java.util.Date;
13 import java.util.List;
16 import java.util.TreeMap;
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.IdentitySchemaNode;
22 import org.opendaylight.yangtools.yang.model.api.Module;
23 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
24 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
25 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
26 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
28 import org.opendaylight.yangtools.yang.parser.builder.api.AugmentationSchemaBuilder;
29 import org.opendaylight.yangtools.yang.parser.builder.api.AugmentationTargetBuilder;
30 import org.opendaylight.yangtools.yang.parser.builder.api.Builder;
31 import org.opendaylight.yangtools.yang.parser.builder.api.DataNodeContainerBuilder;
32 import org.opendaylight.yangtools.yang.parser.builder.api.DataSchemaNodeBuilder;
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.ContainerSchemaNodeImpl;
40 import org.opendaylight.yangtools.yang.parser.builder.impl.IdentitySchemaNodeBuilder;
41 import org.opendaylight.yangtools.yang.parser.builder.impl.ListSchemaNodeBuilder.ListSchemaNodeImpl;
42 import org.opendaylight.yangtools.yang.parser.builder.impl.ModuleBuilder;
43 import org.opendaylight.yangtools.yang.parser.builder.impl.NotificationBuilder.NotificationDefinitionImpl;
44 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget
45 import org.opendaylight.yangtools.yang.parser.builder.impl.RpcDefinitionBuilder
47 public final class ParserUtils {
53 * Create new SchemaPath from given path and qname.
57 * @return new SchemaPath from given path and qname
59 public static def SchemaPath createSchemaPath(SchemaPath schemaPath, QName... qname) {
60 val path = new ArrayList<QName>(schemaPath.getPath());
61 path.addAll(Arrays.asList(qname));
62 return new SchemaPath(path, schemaPath.isAbsolute());
66 * Get module import referenced by given prefix.
71 * prefix associated with import
72 * @return ModuleImport based on given prefix
74 public static def ModuleImport getModuleImport(ModuleBuilder builder, String prefix) {
75 for (ModuleImport mi : builder.getModuleImports()) {
76 if (mi.getPrefix().equals(prefix)) {
85 * Find dependent module based on given prefix
88 * all available modules
92 * target module prefix
94 * current line in yang model
95 * @return module builder if found, null otherwise
97 public static def ModuleBuilder findModuleFromBuilders(Map<String, TreeMap<Date, ModuleBuilder>> modules,
98 ModuleBuilder module, String prefix, int line) {
99 var ModuleBuilder dependentModule = null;
100 var Date dependentModuleRevision = null;
102 if (prefix.equals(module.getPrefix())) {
103 dependentModule = module;
105 val ModuleImport dependentModuleImport = getModuleImport(module, prefix);
106 if (dependentModuleImport === null) {
107 throw new YangParseException(module.getName(), line, "No import found with prefix '" + prefix + "'.");
109 val String dependentModuleName = dependentModuleImport.getModuleName();
110 dependentModuleRevision = dependentModuleImport.getRevision();
112 val TreeMap<Date, ModuleBuilder> moduleBuildersByRevision = modules.get(dependentModuleName);
113 if (moduleBuildersByRevision === null) {
116 if (dependentModuleRevision === null) {
117 dependentModule = moduleBuildersByRevision.lastEntry().getValue();
119 dependentModule = moduleBuildersByRevision.get(dependentModuleRevision);
122 return dependentModule;
126 * Find module from context based on prefix.
130 * @param currentModule
133 * current prefix used to reference dependent module
135 * current line in yang model
136 * @return module based on given prefix if found in context, null otherwise
138 public static def Module findModuleFromContext(SchemaContext context, ModuleBuilder currentModule,
139 String prefix, int line) {
140 if (context === null) {
141 throw new YangParseException(currentModule.getName(), line, "Cannot find module with prefix '" + prefix + "'.");
143 val modulesByRevision = new TreeMap<Date, Module>();
145 val dependentModuleImport = ParserUtils.getModuleImport(currentModule, prefix);
146 if (dependentModuleImport === null) {
147 throw new YangParseException(currentModule.getName(), line, "No import found with prefix '" + prefix + "'.");
149 val dependentModuleName = dependentModuleImport.getModuleName();
150 val dependentModuleRevision = dependentModuleImport.getRevision();
152 for (Module contextModule : context.getModules()) {
153 if (contextModule.getName().equals(dependentModuleName)) {
154 var revision = contextModule.getRevision();
155 if (revision === null) {
156 revision = new Date(0L);
158 modulesByRevision.put(revision, contextModule);
162 var Module result = null;
163 if (dependentModuleRevision === null) {
164 result = modulesByRevision.get(modulesByRevision.firstKey());
166 result = modulesByRevision.get(dependentModuleRevision);
172 * Parse XPath string.
176 * @return SchemaPath from given String
178 public static def SchemaPath parseXPathString(String xpathString) {
179 val absolute = xpathString.startsWith("/");
180 val String[] splittedPath = xpathString.split("/");
181 val path = new ArrayList<QName>();
183 for (String pathElement : splittedPath) {
184 if (pathElement.length() > 0) {
185 val String[] splittedElement = pathElement.split(":");
186 if (splittedElement.length == 1) {
187 name = new QName(null, null, null, splittedElement.get(0));
189 name = new QName(null, null, splittedElement.get(0), splittedElement.get(1));
194 return new SchemaPath(path, absolute);
198 * Add all augment's child nodes to given target.
201 * builder of augment statement
203 * augmentation target node
205 public static def void fillAugmentTarget(AugmentationSchemaBuilder augment, DataNodeContainerBuilder target) {
206 for (DataSchemaNodeBuilder child : augment.getChildNodeBuilders()) {
207 val childCopy = CopyUtils.copy(child, target, false);
208 setNodeAugmenting(childCopy, augment);
209 correctNodePath(child, target.getPath());
210 correctNodePath(childCopy, target.getPath());
212 target.addChildNode(childCopy);
213 } catch (YangParseException e) {
214 // more descriptive message
215 throw new YangParseException(augment.getModuleName(), augment.getLine(),
216 "Failed to perform augmentation: " + e.getMessage());
219 for (UsesNodeBuilder usesNode : augment.getUsesNodes()) {
220 val copy = CopyUtils.copyUses(usesNode, target);
221 copy.setAugmenting(true);
222 target.addUsesNode(copy);
226 private static def void setNodeAugmenting(DataSchemaNodeBuilder child, AugmentationSchemaBuilder augment) {
227 child.setAugmenting(true);
228 if (child instanceof DataNodeContainerBuilder) {
229 val DataNodeContainerBuilder dataNodeChild = child as DataNodeContainerBuilder;
230 for (inner : dataNodeChild.getChildNodeBuilders()) {
231 setNodeAugmenting(inner, augment);
233 for (uses : dataNodeChild.getUsesNodes()) {
234 uses.setParentAugment(augment);
235 uses.setAugmenting(true);
237 } else if (child instanceof ChoiceBuilder) {
238 val ChoiceBuilder choiceChild = child as ChoiceBuilder;
239 for (inner : choiceChild.cases) {
240 setNodeAugmenting(inner, augment);
246 * Add all augment's child nodes to given target.
249 * builder of augment statement
251 * augmentation target choice node
253 public static def void fillAugmentTarget(AugmentationSchemaBuilder augment, ChoiceBuilder target) {
254 for (DataSchemaNodeBuilder builder : augment.getChildNodeBuilders()) {
255 val childCopy = CopyUtils.copy(builder, target, false);
256 setNodeAugmenting(childCopy, augment)
257 correctNodePath(builder, target.getPath());
258 correctNodePath(childCopy, target.getPath());
259 target.addCase(childCopy);
261 for (UsesNodeBuilder usesNode : augment.getUsesNodes()) {
262 if (usesNode !== null) {
263 throw new YangParseException(augment.getModuleName(), augment.getLine(),
264 "Error in augment parsing: cannot augment choice with nodes from grouping");
270 * Create new schema path of node based on parent node schema path.
274 * @param parentSchemaPath
275 * schema path of node parent
277 static def void correctNodePath(SchemaNodeBuilder node, SchemaPath parentSchemaPath) {
280 val targetNodePath = new ArrayList<QName>(parentSchemaPath.getPath());
281 targetNodePath.add(node.getQName());
282 node.setPath(new SchemaPath(targetNodePath, true));
284 // set correct path for all child nodes
285 if (node instanceof DataNodeContainerBuilder) {
286 val dataNodeContainer = node as DataNodeContainerBuilder;
287 for (DataSchemaNodeBuilder child : dataNodeContainer.getChildNodeBuilders()) {
288 correctNodePath(child, node.getPath());
292 // set correct path for all cases
293 if (node instanceof ChoiceBuilder) {
294 val choiceBuilder = node as ChoiceBuilder;
295 for (ChoiceCaseBuilder choiceCaseBuilder : choiceBuilder.getCases()) {
296 correctNodePath(choiceCaseBuilder, node.getPath());
303 private static def Builder findNode(Builder firstNodeParent, List<QName> path, String moduleName, int line) {
304 var currentName = "";
305 var currentParent = firstNodeParent;
307 val max = path.size();
310 var qname = path.get(i);
312 currentName = qname.getLocalName();
313 if (currentParent instanceof DataNodeContainerBuilder) {
314 var dataNodeContainerParent = currentParent as DataNodeContainerBuilder;
315 var SchemaNodeBuilder nodeFound = dataNodeContainerParent.getDataChildByName(currentName);
316 // if not found, search in notifications
317 if (nodeFound == null && currentParent instanceof ModuleBuilder) {
318 nodeFound = searchNotifications(currentParent as ModuleBuilder, currentName);
320 // if not found, search in rpcs
321 if (nodeFound == null && currentParent instanceof ModuleBuilder) {
322 nodeFound = searchRpcs(currentParent as ModuleBuilder, currentName);
324 // if not found, search in uses
325 if (nodeFound == null) {
326 var found = searchUses(dataNodeContainerParent, currentName);
330 currentParent = found;
333 currentParent = nodeFound;
335 } else if (currentParent instanceof ChoiceBuilder) {
336 val choiceParent = currentParent as ChoiceBuilder;
337 currentParent = choiceParent.getCaseNodeByName(currentName);
338 } else if (currentParent instanceof RpcDefinitionBuilder) {
339 val rpc = currentParent as RpcDefinitionBuilder;
340 if ("input".equals(currentName)) {
341 currentParent = rpc.input;
342 } else if ("output".equals(currentName)) {
343 currentParent = rpc.output;
346 throw new YangParseException(moduleName, line,
347 "Error in augment parsing: failed to find node " + currentName);
350 // if node in path not found, return null
351 if (currentParent == null) {
356 return currentParent;
359 private static def searchNotifications(ModuleBuilder parent, String name) {
360 for(notification : parent.notifications) {
361 if(notification.getQName().localName.equals(name)) {
368 private static def searchRpcs(ModuleBuilder parent, String name) {
369 for(rpc : parent.rpcs) {
370 if(rpc.getQName().localName.equals(name)) {
377 private static def searchUses(DataNodeContainerBuilder dataNodeContainerParent, String name) {
378 var currentName = name;
379 for (unb : dataNodeContainerParent.usesNodes) {
380 var result = searchInUsesTarget(currentName, unb);
381 if (result != null) {
385 result = findNodeInUses(currentName, unb);
386 if (result != null) {
387 var copy = CopyUtils.copy(result, unb.getParent(), true);
388 unb.getTargetChildren().add(copy);
395 public static def getRpc(ModuleBuilder module,String name) {
396 for(rpc : module.getRpcs()) {
397 if(name == rpc.QName.localName) {
404 public static def getNotification(ModuleBuilder module,String name) {
405 for(notification : module.getNotifications()) {
406 if(name == notification.QName.localName) {
412 private static def nextLevel(List<QName> path){
413 return path.subList(1,path.size)
417 * Find augment target node and perform augmentation.
420 * @param firstNodeParent
421 * parent of first node in path
423 * path to augment target
424 * @return true if augmentation process succeed, false otherwise
426 public static def boolean processAugmentation(AugmentationSchemaBuilder augment, Builder firstNodeParent,
429 // traverse augment target path and try to reach target node
430 val targetNode = findNode(firstNodeParent,path,augment.moduleName,augment.line);
431 if (targetNode === null) return false;
433 if ((targetNode instanceof DataNodeContainerBuilder)) {
434 val targetDataNodeContainer = targetNode as DataNodeContainerBuilder;
435 augment.setTargetNodeSchemaPath(targetDataNodeContainer.getPath());
436 fillAugmentTarget(augment, targetDataNodeContainer);
437 } else if (targetNode instanceof ChoiceBuilder) {
438 val targetChoiceBuilder = targetNode as ChoiceBuilder;
439 augment.setTargetNodeSchemaPath(targetChoiceBuilder.getPath());
440 fillAugmentTarget(augment, targetChoiceBuilder);
442 throw new YangParseException(augment.getModuleName(), augment.getLine(),
443 "Error in augment parsing: The target node MUST be either a container, list, choice, case, input, output, or notification node.");
445 (targetNode as AugmentationTargetBuilder).addAugmentation(augment);
446 augment.setResolved(true);
450 private static def DataSchemaNodeBuilder searchInUsesTarget(String localName, UsesNodeBuilder uses) {
451 for(child : uses.targetChildren) {
452 if (child.getQName().getLocalName().equals(localName)) {
459 * Find node with given name in uses target.
462 * name of node to find
464 * uses node which target grouping should be searched
465 * @return node with given name if found, null otherwise
467 private static def DataSchemaNodeBuilder findNodeInUses(String localName, UsesNodeBuilder uses) {
468 val target = uses.groupingBuilder;
469 for (child : target.childNodeBuilders) {
470 if (child.getQName().getLocalName().equals(localName)) {
474 for (usesNode : target.usesNodes) {
475 val result = findNodeInUses(localName, usesNode);
476 if (result != null) {
484 * Find augment target node in given context and perform augmentation.
488 * path to augment target
492 * current prefix of target module
494 * SchemaContext containing already resolved modules
495 * @return true if augment process succeed, false otherwise
497 public static def boolean processAugmentationOnContext(AugmentationSchemaBuilder augment, List<QName> path,
498 ModuleBuilder module, String prefix, SchemaContext context) {
499 val int line = augment.getLine();
500 val Module dependentModule = findModuleFromContext(context, module, prefix, line);
501 if (dependentModule === null) {
502 throw new YangParseException(module.getName(), line,
503 "Error in augment parsing: failed to find module with prefix " + prefix + ".");
506 var currentName = path.get(0).getLocalName();
507 var SchemaNode currentParent = dependentModule.getDataChildByName(currentName);
508 if (currentParent === null) {
509 val notifications = dependentModule.getNotifications();
510 for (NotificationDefinition ntf : notifications) {
511 if (ntf.getQName().getLocalName().equals(currentName)) {
516 if (currentParent === null) {
517 throw new YangParseException(module.getName(), line,
518 "Error in augment parsing: failed to find node " + currentName + ".");
521 for (qname : path.nextLevel) {
522 currentName = qname.getLocalName();
523 if (currentParent instanceof DataNodeContainer) {
524 currentParent = (currentParent as DataNodeContainer).getDataChildByName(currentName);
525 } else if (currentParent instanceof ChoiceNode) {
526 currentParent = (currentParent as ChoiceNode).getCaseNodeByName(currentName);
528 throw new YangParseException(augment.getModuleName(), line,
529 "Error in augment parsing: failed to find node " + currentName);
532 // if node in path not found, return false
533 if (currentParent === null) {
534 throw new YangParseException(module.getName(), line,
535 "Error in augment parsing: failed to find node " + currentName + ".");
539 val oldPath = currentParent.path;
541 if (!(currentParent instanceof AugmentationTarget)) {
542 throw new YangParseException(module.getName(), line,
543 "Target of type " + currentParent.class + " cannot be augmented.");
546 switch (currentParent) {
547 case (currentParent instanceof ContainerSchemaNodeImpl): {
549 // includes container, input and output statement
550 val c = currentParent as ContainerSchemaNodeImpl;
551 val cb = c.toBuilder();
552 fillAugmentTarget(augment, cb);
553 (cb as AugmentationTargetBuilder ).addAugmentation(augment);
556 case (currentParent instanceof ListSchemaNodeImpl): {
557 val l = currentParent as ListSchemaNodeImpl;
558 val lb = l.toBuilder();
559 fillAugmentTarget(augment, lb);
560 (lb as AugmentationTargetBuilder ).addAugmentation(augment);
562 augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
563 augment.setResolved(true);
565 case (currentParent instanceof ChoiceNodeImpl): {
566 val ch = currentParent as ChoiceNodeImpl;
567 val chb = ch.toBuilder();
568 fillAugmentTarget(augment, chb);
569 (chb as AugmentationTargetBuilder ).addAugmentation(augment);
571 augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
572 augment.setResolved(true);
574 case (currentParent instanceof ChoiceCaseNodeImpl): {
575 val chc = currentParent as ChoiceCaseNodeImpl;
576 val chcb = chc.toBuilder();
577 fillAugmentTarget(augment, chcb);
578 (chcb as AugmentationTargetBuilder ).addAugmentation(augment);
580 augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
581 augment.setResolved(true);
583 case (currentParent instanceof NotificationDefinitionImpl): {
584 val nd = currentParent as NotificationDefinitionImpl;
585 val nb = nd.toBuilder();
586 fillAugmentTarget(augment, nb);
587 (nb as AugmentationTargetBuilder ).addAugmentation(augment);
589 augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
590 augment.setResolved(true);
593 augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
594 augment.setResolved(true);
598 public static def IdentitySchemaNodeBuilder findBaseIdentity(Map<String, TreeMap<Date, ModuleBuilder>> modules,
599 ModuleBuilder module, String baseString, int line) {
600 var IdentitySchemaNodeBuilder result = null;
601 if (baseString.contains(":")) {
602 val String[] splittedBase = baseString.split(":");
603 if (splittedBase.length > 2) {
604 throw new YangParseException(module.getName(), line, "Failed to parse identityref base: " +
607 val prefix = splittedBase.get(0);
608 val name = splittedBase.get(1);
609 val dependentModule = findModuleFromBuilders(modules, module, prefix, line);
610 if (dependentModule !== null) {
611 result = findIdentity(dependentModule.identities, name);
614 result = findIdentity(module.identities, baseString);
619 public static def IdentitySchemaNode findBaseIdentityFromContext(Map<String, TreeMap<Date, ModuleBuilder>> modules,
620 ModuleBuilder module, String baseString, int line, SchemaContext context) {
621 var IdentitySchemaNode result = null;
623 val String[] splittedBase = baseString.split(":");
624 if (splittedBase.length > 2) {
625 throw new YangParseException(module.getName(), line, "Failed to parse identityref base: " + baseString);
627 val prefix = splittedBase.get(0);
628 val name = splittedBase.get(1);
629 val dependentModule = findModuleFromContext(context, module, prefix, line);
630 result = findIdentityNode(dependentModule.identities, name);
632 if (result == null) {
633 throw new YangParseException(module.name, line, "Failed to find base identity");
638 private static def IdentitySchemaNodeBuilder findIdentity(Set<IdentitySchemaNodeBuilder> identities, String name) {
639 for (identity : identities) {
640 if (identity.QName.localName.equals(name)) {
647 private static def IdentitySchemaNode findIdentityNode(Set<IdentitySchemaNode> identities, String name) {
648 for (identity : identities) {
649 if (identity.QName.localName.equals(name)) {
657 * Get module in which this node is defined.
660 * @return builder of module where this node is defined
662 public static def ModuleBuilder getParentModule(Builder node) {
663 if (node instanceof ModuleBuilder) {
664 return node as ModuleBuilder;
666 var parent = node.getParent();
667 while (!(parent instanceof ModuleBuilder)) {
668 parent = parent.getParent();
670 return parent as ModuleBuilder;