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 target.addUsesNode(copy);
225 private static def void setNodeAugmenting(DataSchemaNodeBuilder child, AugmentationSchemaBuilder augment) {
226 child.setAugmenting(true);
227 if (child instanceof DataNodeContainerBuilder) {
228 val DataNodeContainerBuilder dataNodeChild = child as DataNodeContainerBuilder;
229 for (inner : dataNodeChild.getChildNodeBuilders()) {
230 setNodeAugmenting(inner, augment);
232 for (uses : dataNodeChild.getUsesNodes()) {
233 uses.setParentAugment(augment);
234 uses.setAugmenting(true);
236 } else if (child instanceof ChoiceBuilder) {
237 val ChoiceBuilder choiceChild = child as ChoiceBuilder;
238 for (inner : choiceChild.cases) {
239 setNodeAugmenting(inner, augment);
245 * Add all augment's child nodes to given target.
248 * builder of augment statement
250 * augmentation target choice node
252 public static def void fillAugmentTarget(AugmentationSchemaBuilder augment, ChoiceBuilder target) {
253 for (DataSchemaNodeBuilder builder : augment.getChildNodeBuilders()) {
254 val childCopy = CopyUtils.copy(builder, target, false);
255 setNodeAugmenting(childCopy, augment)
256 correctNodePath(builder, target.getPath());
257 correctNodePath(childCopy, target.getPath());
258 target.addCase(childCopy);
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");
269 * Create new schema path of node based on parent node schema path.
273 * @param parentSchemaPath
274 * schema path of node parent
276 static def void correctNodePath(SchemaNodeBuilder node, SchemaPath parentSchemaPath) {
279 val targetNodePath = new ArrayList<QName>(parentSchemaPath.getPath());
280 targetNodePath.add(node.getQName());
281 node.setPath(new SchemaPath(targetNodePath, true));
283 // set correct path for all child nodes
284 if (node instanceof DataNodeContainerBuilder) {
285 val dataNodeContainer = node as DataNodeContainerBuilder;
286 for (DataSchemaNodeBuilder child : dataNodeContainer.getChildNodeBuilders()) {
287 correctNodePath(child, node.getPath());
291 // set correct path for all cases
292 if (node instanceof ChoiceBuilder) {
293 val choiceBuilder = node as ChoiceBuilder;
294 for (ChoiceCaseBuilder choiceCaseBuilder : choiceBuilder.getCases()) {
295 correctNodePath(choiceCaseBuilder, node.getPath());
302 private static def Builder findNode(Builder firstNodeParent, List<QName> path, String moduleName, int line) {
303 var currentName = "";
304 var currentParent = firstNodeParent;
306 val max = path.size();
309 var qname = path.get(i);
311 currentName = qname.getLocalName();
312 if (currentParent instanceof DataNodeContainerBuilder) {
313 var dataNodeContainerParent = currentParent as DataNodeContainerBuilder;
314 var SchemaNodeBuilder nodeFound = dataNodeContainerParent.getDataChildByName(currentName);
315 // if not found, search in notifications
316 if (nodeFound == null && currentParent instanceof ModuleBuilder) {
317 nodeFound = searchNotifications(currentParent as ModuleBuilder, currentName);
319 // if not found, search in rpcs
320 if (nodeFound == null && currentParent instanceof ModuleBuilder) {
321 nodeFound = searchRpcs(currentParent as ModuleBuilder, currentName);
323 // if not found, search in uses
324 if (nodeFound == null) {
325 var found = searchUses(dataNodeContainerParent, currentName);
329 currentParent = found;
332 currentParent = nodeFound;
334 } else if (currentParent instanceof ChoiceBuilder) {
335 val choiceParent = currentParent as ChoiceBuilder;
336 currentParent = choiceParent.getCaseNodeByName(currentName);
337 } else if (currentParent instanceof RpcDefinitionBuilder) {
338 val rpc = currentParent as RpcDefinitionBuilder;
339 if ("input".equals(currentName)) {
340 currentParent = rpc.input;
341 } else if ("output".equals(currentName)) {
342 currentParent = rpc.output;
345 throw new YangParseException(moduleName, line,
346 "Error in augment parsing: failed to find node " + currentName);
349 // if node in path not found, return null
350 if (currentParent == null) {
355 return currentParent;
358 private static def searchNotifications(ModuleBuilder parent, String name) {
359 for(notification : parent.notifications) {
360 if(notification.getQName().localName.equals(name)) {
367 private static def searchRpcs(ModuleBuilder parent, String name) {
368 for(rpc : parent.rpcs) {
369 if(rpc.getQName().localName.equals(name)) {
376 private static def searchUses(DataNodeContainerBuilder dataNodeContainerParent, String name) {
377 var currentName = name;
378 for (unb : dataNodeContainerParent.usesNodes) {
379 var result = searchInUsesTarget(currentName, unb);
380 if (result != null) {
384 result = findNodeInUses(currentName, unb);
385 if (result != null) {
386 var copy = CopyUtils.copy(result, unb.getParent(), true);
387 unb.getTargetChildren().add(copy);
394 public static def getRpc(ModuleBuilder module,String name) {
395 for(rpc : module.rpcs) {
396 if(name == rpc.QName.localName) {
403 public static def getNotification(ModuleBuilder module,String name) {
404 for(notification : module.notifications) {
405 if(name == notification.QName.localName) {
411 private static def nextLevel(List<QName> path){
412 return path.subList(1,path.size)
416 * Find augment target node and perform augmentation.
419 * @param firstNodeParent
420 * parent of first node in path
422 * path to augment target
423 * @return true if augmentation process succeed, false otherwise
425 public static def boolean processAugmentation(AugmentationSchemaBuilder augment, Builder firstNodeParent,
428 // traverse augment target path and try to reach target node
429 val targetNode = findNode(firstNodeParent,path,augment.moduleName,augment.line);
430 if (targetNode === null) return false;
432 if ((targetNode instanceof DataNodeContainerBuilder)) {
433 val targetDataNodeContainer = targetNode as DataNodeContainerBuilder;
434 augment.setTargetNodeSchemaPath(targetDataNodeContainer.getPath());
435 fillAugmentTarget(augment, targetDataNodeContainer);
436 } else if (targetNode instanceof ChoiceBuilder) {
437 val targetChoiceBuilder = targetNode as ChoiceBuilder;
438 augment.setTargetNodeSchemaPath(targetChoiceBuilder.getPath());
439 fillAugmentTarget(augment, targetChoiceBuilder);
441 throw new YangParseException(augment.getModuleName(), augment.getLine(),
442 "Error in augment parsing: The target node MUST be either a container, list, choice, case, input, output, or notification node.");
444 (targetNode as AugmentationTargetBuilder).addAugmentation(augment);
445 augment.setResolved(true);
449 private static def DataSchemaNodeBuilder searchInUsesTarget(String localName, UsesNodeBuilder uses) {
450 for(child : uses.targetChildren) {
451 if (child.getQName().getLocalName().equals(localName)) {
458 * Find node with given name in uses target.
461 * name of node to find
463 * uses node which target grouping should be searched
464 * @return node with given name if found, null otherwise
466 private static def DataSchemaNodeBuilder findNodeInUses(String localName, UsesNodeBuilder uses) {
467 val target = uses.groupingBuilder;
468 for (child : target.childNodeBuilders) {
469 if (child.getQName().getLocalName().equals(localName)) {
473 for (usesNode : target.usesNodes) {
474 val result = findNodeInUses(localName, usesNode);
475 if (result != null) {
483 * Find augment target node in given context and perform augmentation.
487 * path to augment target
491 * current prefix of target module
493 * SchemaContext containing already resolved modules
494 * @return true if augment process succeed, false otherwise
496 public static def boolean processAugmentationOnContext(AugmentationSchemaBuilder augment, List<QName> path,
497 ModuleBuilder module, String prefix, SchemaContext context) {
498 val int line = augment.getLine();
499 val Module dependentModule = findModuleFromContext(context, module, prefix, line);
500 if (dependentModule === null) {
501 throw new YangParseException(module.getName(), line,
502 "Error in augment parsing: failed to find module with prefix " + prefix + ".");
505 var currentName = path.get(0).getLocalName();
506 var SchemaNode currentParent = dependentModule.getDataChildByName(currentName);
507 if (currentParent === null) {
508 val notifications = dependentModule.getNotifications();
509 for (NotificationDefinition ntf : notifications) {
510 if (ntf.getQName().getLocalName().equals(currentName)) {
515 if (currentParent === null) {
516 throw new YangParseException(module.getName(), line,
517 "Error in augment parsing: failed to find node " + currentName + ".");
520 for (qname : path.nextLevel) {
521 currentName = qname.getLocalName();
522 if (currentParent instanceof DataNodeContainer) {
523 currentParent = (currentParent as DataNodeContainer).getDataChildByName(currentName);
524 } else if (currentParent instanceof ChoiceNode) {
525 currentParent = (currentParent as ChoiceNode).getCaseNodeByName(currentName);
527 throw new YangParseException(augment.getModuleName(), line,
528 "Error in augment parsing: failed to find node " + currentName);
531 // if node in path not found, return false
532 if (currentParent === null) {
533 throw new YangParseException(module.getName(), line,
534 "Error in augment parsing: failed to find node " + currentName + ".");
538 val oldPath = currentParent.path;
540 if (!(currentParent instanceof AugmentationTarget)) {
541 throw new YangParseException(module.getName(), line,
542 "Target of type " + currentParent.class + " cannot be augmented.");
545 switch (currentParent) {
546 case (currentParent instanceof ContainerSchemaNodeImpl): {
548 // includes container, input and output statement
549 val c = currentParent as ContainerSchemaNodeImpl;
550 val cb = c.toBuilder();
551 fillAugmentTarget(augment, cb);
552 (cb as AugmentationTargetBuilder ).addAugmentation(augment);
555 case (currentParent instanceof ListSchemaNodeImpl): {
556 val l = currentParent as ListSchemaNodeImpl;
557 val lb = l.toBuilder();
558 fillAugmentTarget(augment, lb);
559 (lb as AugmentationTargetBuilder ).addAugmentation(augment);
561 augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
562 augment.setResolved(true);
564 case (currentParent instanceof ChoiceNodeImpl): {
565 val ch = currentParent as ChoiceNodeImpl;
566 val chb = ch.toBuilder();
567 fillAugmentTarget(augment, chb);
568 (chb as AugmentationTargetBuilder ).addAugmentation(augment);
570 augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
571 augment.setResolved(true);
573 case (currentParent instanceof ChoiceCaseNodeImpl): {
574 val chc = currentParent as ChoiceCaseNodeImpl;
575 val chcb = chc.toBuilder();
576 fillAugmentTarget(augment, chcb);
577 (chcb as AugmentationTargetBuilder ).addAugmentation(augment);
579 augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
580 augment.setResolved(true);
582 case (currentParent instanceof NotificationDefinitionImpl): {
583 val nd = currentParent as NotificationDefinitionImpl;
584 val nb = nd.toBuilder();
585 fillAugmentTarget(augment, nb);
586 (nb as AugmentationTargetBuilder ).addAugmentation(augment);
588 augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
589 augment.setResolved(true);
592 augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
593 augment.setResolved(true);
597 public static def IdentitySchemaNodeBuilder findBaseIdentity(Map<String, TreeMap<Date, ModuleBuilder>> modules,
598 ModuleBuilder module, String baseString, int line) {
599 var IdentitySchemaNodeBuilder result = null;
600 if (baseString.contains(":")) {
601 val String[] splittedBase = baseString.split(":");
602 if (splittedBase.length > 2) {
603 throw new YangParseException(module.getName(), line, "Failed to parse identityref base: " +
606 val prefix = splittedBase.get(0);
607 val name = splittedBase.get(1);
608 val dependentModule = findModuleFromBuilders(modules, module, prefix, line);
609 if (dependentModule !== null) {
610 result = findIdentity(dependentModule.identities, name);
613 result = findIdentity(module.identities, baseString);
618 public static def IdentitySchemaNode findBaseIdentityFromContext(Map<String, TreeMap<Date, ModuleBuilder>> modules,
619 ModuleBuilder module, String baseString, int line, SchemaContext context) {
620 var IdentitySchemaNode result = null;
622 val String[] splittedBase = baseString.split(":");
623 if (splittedBase.length > 2) {
624 throw new YangParseException(module.getName(), line, "Failed to parse identityref base: " + baseString);
626 val prefix = splittedBase.get(0);
627 val name = splittedBase.get(1);
628 val dependentModule = findModuleFromContext(context, module, prefix, line);
629 result = findIdentityNode(dependentModule.identities, name);
631 if (result == null) {
632 throw new YangParseException(module.name, line, "Failed to find base identity");
637 private static def IdentitySchemaNodeBuilder findIdentity(Set<IdentitySchemaNodeBuilder> identities, String name) {
638 for (identity : identities) {
639 if (identity.QName.localName.equals(name)) {
646 private static def IdentitySchemaNode findIdentityNode(Set<IdentitySchemaNode> identities, String name) {
647 for (identity : identities) {
648 if (identity.QName.localName.equals(name)) {
656 * Get module in which this node is defined.
659 * @return builder of module where this node is defined
661 public static def ModuleBuilder getParentModule(Builder node) {
662 if (node instanceof ModuleBuilder) {
663 return node as ModuleBuilder;
665 var parent = node.getParent();
666 while (!(parent instanceof ModuleBuilder)) {
667 parent = parent.getParent();
669 return parent as ModuleBuilder;