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 import org.opendaylight.yangtools.yang.parser.builder.api.GroupingMember
49 public final class ParserUtils {
55 * Create new SchemaPath from given path and qname.
59 * @return new SchemaPath from given path and qname
61 public static def SchemaPath createSchemaPath(SchemaPath schemaPath, QName... qname) {
62 val path = new ArrayList<QName>(schemaPath.getPath());
63 path.addAll(Arrays.asList(qname));
64 return new SchemaPath(path, schemaPath.isAbsolute());
67 public static def SchemaPath correctSchemaPath(SchemaPath old, URI ns, Date revision, String prefix) {
68 val List<QName> newPath = new ArrayList();
69 for (name : old.path) {
70 newPath.add(new QName(ns, revision, prefix, name.localName))
72 return new SchemaPath(newPath, old.absolute)
76 * Get module import referenced by given prefix.
81 * prefix associated with import
82 * @return ModuleImport based on given prefix
84 public static def ModuleImport getModuleImport(ModuleBuilder builder, String prefix) {
85 for (ModuleImport mi : builder.getModuleImports()) {
86 if (mi.getPrefix().equals(prefix)) {
95 * Find dependent module based on given prefix
98 * all available modules
102 * target module prefix
104 * current line in yang model
105 * @return module builder if found, null otherwise
107 public static def ModuleBuilder findModuleFromBuilders(Map<String, TreeMap<Date, ModuleBuilder>> modules,
108 ModuleBuilder module, String prefix, int line) {
109 var ModuleBuilder dependentModule = null;
110 var Date dependentModuleRevision = null;
112 if (prefix.equals(module.getPrefix())) {
113 dependentModule = module;
115 val ModuleImport dependentModuleImport = getModuleImport(module, prefix);
116 if (dependentModuleImport === null) {
117 throw new YangParseException(module.getName(), line, "No import found with prefix '" + prefix + "'.");
119 val String dependentModuleName = dependentModuleImport.getModuleName();
120 dependentModuleRevision = dependentModuleImport.getRevision();
122 val TreeMap<Date, ModuleBuilder> moduleBuildersByRevision = modules.get(dependentModuleName);
123 if (moduleBuildersByRevision === null) {
126 if (dependentModuleRevision === null) {
127 dependentModule = moduleBuildersByRevision.lastEntry().getValue();
129 dependentModule = moduleBuildersByRevision.get(dependentModuleRevision);
132 return dependentModule;
136 * Find module from context based on prefix.
140 * @param currentModule
143 * current prefix used to reference dependent module
145 * current line in yang model
146 * @return module based on given prefix if found in context, null otherwise
148 public static def Module findModuleFromContext(SchemaContext context, ModuleBuilder currentModule,
149 String prefix, int line) {
150 if (context === null) {
151 throw new YangParseException(currentModule.getName(), line, "Cannot find module with prefix '" + prefix + "'.");
153 val modulesByRevision = new TreeMap<Date, Module>();
155 val dependentModuleImport = ParserUtils.getModuleImport(currentModule, prefix);
156 if (dependentModuleImport === null) {
157 throw new YangParseException(currentModule.getName(), line, "No import found with prefix '" + prefix + "'.");
159 val dependentModuleName = dependentModuleImport.getModuleName();
160 val dependentModuleRevision = dependentModuleImport.getRevision();
162 for (Module contextModule : context.getModules()) {
163 if (contextModule.getName().equals(dependentModuleName)) {
164 var revision = contextModule.getRevision();
165 if (revision === null) {
166 revision = new Date(0L);
168 modulesByRevision.put(revision, contextModule);
172 var Module result = null;
173 if (dependentModuleRevision === null) {
174 result = modulesByRevision.get(modulesByRevision.firstKey());
176 result = modulesByRevision.get(dependentModuleRevision);
182 * Parse XPath string.
186 * @return SchemaPath from given String
188 public static def SchemaPath parseXPathString(String xpathString) {
189 val absolute = xpathString.startsWith("/");
190 val String[] splittedPath = xpathString.split("/");
191 val path = new ArrayList<QName>();
193 for (String pathElement : splittedPath) {
194 if (pathElement.length() > 0) {
195 val String[] splittedElement = pathElement.split(":");
196 if (splittedElement.length == 1) {
197 name = new QName(null, null, null, splittedElement.get(0));
199 name = new QName(null, null, splittedElement.get(0), splittedElement.get(1));
204 return new SchemaPath(path, absolute);
208 * Add all augment's child nodes to given target.
211 * builder of augment statement
213 * augmentation target node
215 public static def void fillAugmentTarget(AugmentationSchemaBuilder augment, DataNodeContainerBuilder target) {
216 for (DataSchemaNodeBuilder child : augment.getChildNodeBuilders()) {
217 val childCopy = CopyUtils.copy(child, target, false);
218 if (augment.parent instanceof UsesNodeBuilder) {
219 setNodeAddedByUses(childCopy);
221 setNodeAugmenting(childCopy);
222 correctNodePath(child, target.getPath());
223 correctNodePath(childCopy, target.getPath());
225 target.addChildNode(childCopy);
226 } catch (YangParseException e) {
227 // more descriptive message
228 throw new YangParseException(augment.getModuleName(), augment.getLine(),
229 "Failed to perform augmentation: " + e.getMessage());
234 private static def void setNodeAugmenting(DataSchemaNodeBuilder child) {
235 child.setAugmenting(true);
236 if (child instanceof DataNodeContainerBuilder) {
237 val DataNodeContainerBuilder dataNodeChild = child as DataNodeContainerBuilder;
238 for (inner : dataNodeChild.getChildNodeBuilders()) {
239 setNodeAugmenting(inner);
241 } else if (child instanceof ChoiceBuilder) {
242 val ChoiceBuilder choiceChild = child as ChoiceBuilder;
243 for (inner : choiceChild.cases) {
244 setNodeAugmenting(inner);
249 public static def void setNodeAddedByUses(GroupingMember child) {
250 child.setAddedByUses(true);
251 if (child instanceof DataNodeContainerBuilder) {
252 val DataNodeContainerBuilder dataNodeChild = child as DataNodeContainerBuilder;
253 for (inner : dataNodeChild.getChildNodeBuilders()) {
254 setNodeAddedByUses(inner);
256 } else if (child instanceof ChoiceBuilder) {
257 val ChoiceBuilder choiceChild = child as ChoiceBuilder;
258 for (inner : choiceChild.cases) {
259 setNodeAddedByUses(inner);
265 * Add all augment's child nodes to given target.
268 * builder of augment statement
270 * augmentation target choice node
272 public static def void fillAugmentTarget(AugmentationSchemaBuilder augment, ChoiceBuilder target) {
273 for (DataSchemaNodeBuilder builder : augment.getChildNodeBuilders()) {
274 val childCopy = CopyUtils.copy(builder, target, false);
275 if (augment.parent instanceof UsesNodeBuilder) {
276 setNodeAddedByUses(childCopy);
278 setNodeAugmenting(childCopy)
279 correctNodePath(builder, target.getPath());
280 correctNodePath(childCopy, target.getPath());
281 target.addCase(childCopy);
283 for (UsesNodeBuilder usesNode : augment.getUsesNodes()) {
284 if (usesNode !== null) {
285 throw new YangParseException(augment.getModuleName(), augment.getLine(),
286 "Error in augment parsing: cannot augment choice with nodes from grouping");
292 * Create new schema path of node based on parent node schema path.
296 * @param parentSchemaPath
297 * schema path of node parent
299 static def void correctNodePath(SchemaNodeBuilder node, SchemaPath parentSchemaPath) {
302 val targetNodePath = new ArrayList<QName>(parentSchemaPath.getPath());
303 targetNodePath.add(node.getQName());
304 node.setPath(new SchemaPath(targetNodePath, true));
306 // set correct path for all child nodes
307 if (node instanceof DataNodeContainerBuilder) {
308 val dataNodeContainer = node as DataNodeContainerBuilder;
309 for (DataSchemaNodeBuilder child : dataNodeContainer.getChildNodeBuilders()) {
310 correctNodePath(child, node.getPath());
314 // set correct path for all cases
315 if (node instanceof ChoiceBuilder) {
316 val choiceBuilder = node as ChoiceBuilder;
317 for (ChoiceCaseBuilder choiceCaseBuilder : choiceBuilder.getCases()) {
318 correctNodePath(choiceCaseBuilder, node.getPath());
325 private static def Builder findNode(Builder firstNodeParent, List<QName> path, String moduleName, int line) {
326 var currentName = "";
327 var currentParent = firstNodeParent;
329 val max = path.size();
332 var qname = path.get(i);
334 currentName = qname.getLocalName();
335 if (currentParent instanceof DataNodeContainerBuilder) {
336 var dataNodeContainerParent = currentParent as DataNodeContainerBuilder;
337 var SchemaNodeBuilder nodeFound = dataNodeContainerParent.getDataChildByName(currentName);
338 // if not found, search in notifications
339 if (nodeFound == null && currentParent instanceof ModuleBuilder) {
340 nodeFound = searchNotifications(currentParent as ModuleBuilder, currentName);
342 // if not found, search in rpcs
343 if (nodeFound == null && currentParent instanceof ModuleBuilder) {
344 nodeFound = searchRpcs(currentParent as ModuleBuilder, currentName);
346 // if not found, search in uses
347 if (nodeFound == null) {
348 var found = searchUses(dataNodeContainerParent, currentName);
352 currentParent = found;
355 currentParent = nodeFound;
357 } else if (currentParent instanceof ChoiceBuilder) {
358 val choiceParent = currentParent as ChoiceBuilder;
359 currentParent = choiceParent.getCaseNodeByName(currentName);
360 } else if (currentParent instanceof RpcDefinitionBuilder) {
361 val rpc = currentParent as RpcDefinitionBuilder;
362 if ("input".equals(currentName)) {
363 currentParent = rpc.input;
364 } else if ("output".equals(currentName)) {
365 currentParent = rpc.output;
368 throw new YangParseException(moduleName, line,
369 "Error in augment parsing: failed to find node " + currentName);
372 // if node in path not found, return null
373 if (currentParent == null) {
378 return currentParent;
381 private static def searchNotifications(ModuleBuilder parent, String name) {
382 for(notification : parent.notifications) {
383 if(notification.getQName().localName.equals(name)) {
390 private static def searchRpcs(ModuleBuilder parent, String name) {
391 for(rpc : parent.rpcs) {
392 if(rpc.getQName().localName.equals(name)) {
399 private static def searchUses(DataNodeContainerBuilder dataNodeContainerParent, String name) {
400 var currentName = name;
401 for (unb : dataNodeContainerParent.usesNodes) {
402 var result = searchInUsesTarget(currentName, unb);
403 if (result != null) {
407 result = findNodeInUses(currentName, unb);
408 if (result != null) {
409 var copy = CopyUtils.copy(result, unb.getParent(), true);
410 unb.getTargetChildren().add(copy);
417 public static def getRpc(ModuleBuilder module,String name) {
418 for(rpc : module.getRpcs()) {
419 if(name == rpc.QName.localName) {
426 public static def getNotification(ModuleBuilder module,String name) {
427 for(notification : module.getNotifications()) {
428 if(name == notification.QName.localName) {
434 private static def nextLevel(List<QName> path){
435 return path.subList(1,path.size)
439 * Find augment target node and perform augmentation.
442 * @param firstNodeParent
443 * parent of first node in path
445 * path to augment target
446 * @return true if augmentation process succeed, false otherwise
448 public static def boolean processAugmentation(AugmentationSchemaBuilder augment, Builder firstNodeParent,
451 // traverse augment target path and try to reach target node
452 val targetNode = findNode(firstNodeParent,path,augment.moduleName,augment.line);
453 if (targetNode === null) return false;
455 if ((targetNode instanceof DataNodeContainerBuilder)) {
456 val targetDataNodeContainer = targetNode as DataNodeContainerBuilder;
457 augment.setTargetNodeSchemaPath(targetDataNodeContainer.getPath());
458 fillAugmentTarget(augment, targetDataNodeContainer);
459 } else if (targetNode instanceof ChoiceBuilder) {
460 val targetChoiceBuilder = targetNode as ChoiceBuilder;
461 augment.setTargetNodeSchemaPath(targetChoiceBuilder.getPath());
462 fillAugmentTarget(augment, targetChoiceBuilder);
464 throw new YangParseException(augment.getModuleName(), augment.getLine(),
465 "Error in augment parsing: The target node MUST be either a container, list, choice, case, input, output, or notification node.");
467 (targetNode as AugmentationTargetBuilder).addAugmentation(augment);
468 augment.setResolved(true);
472 private static def DataSchemaNodeBuilder searchInUsesTarget(String localName, UsesNodeBuilder uses) {
473 for(child : uses.targetChildren) {
474 if (child.getQName().getLocalName().equals(localName)) {
481 * Find node with given name in uses target.
484 * name of node to find
486 * uses node which target grouping should be searched
487 * @return node with given name if found, null otherwise
489 private static def DataSchemaNodeBuilder findNodeInUses(String localName, UsesNodeBuilder uses) {
490 val target = uses.groupingBuilder;
491 for (child : target.childNodeBuilders) {
492 if (child.getQName().getLocalName().equals(localName)) {
496 for (usesNode : target.usesNodes) {
497 val result = findNodeInUses(localName, usesNode);
498 if (result != null) {
506 * Find augment target node in given context and perform augmentation.
510 * path to augment target
514 * current prefix of target module
516 * SchemaContext containing already resolved modules
517 * @return true if augment process succeed, false otherwise
519 public static def boolean processAugmentationOnContext(AugmentationSchemaBuilder augment, List<QName> path,
520 ModuleBuilder module, String prefix, SchemaContext context) {
521 val int line = augment.getLine();
522 val Module dependentModule = findModuleFromContext(context, module, prefix, line);
523 if (dependentModule === null) {
524 throw new YangParseException(module.getName(), line,
525 "Error in augment parsing: failed to find module with prefix " + prefix + ".");
528 var currentName = path.get(0).getLocalName();
529 var SchemaNode currentParent = dependentModule.getDataChildByName(currentName);
530 if (currentParent === null) {
531 val notifications = dependentModule.getNotifications();
532 for (NotificationDefinition ntf : notifications) {
533 if (ntf.getQName().getLocalName().equals(currentName)) {
538 if (currentParent === null) {
539 throw new YangParseException(module.getName(), line,
540 "Error in augment parsing: failed to find node " + currentName + ".");
543 for (qname : path.nextLevel) {
544 currentName = qname.getLocalName();
545 if (currentParent instanceof DataNodeContainer) {
546 currentParent = (currentParent as DataNodeContainer).getDataChildByName(currentName);
547 } else if (currentParent instanceof ChoiceNode) {
548 currentParent = (currentParent as ChoiceNode).getCaseNodeByName(currentName);
550 throw new YangParseException(augment.getModuleName(), line,
551 "Error in augment parsing: failed to find node " + currentName);
554 // if node in path not found, return false
555 if (currentParent === null) {
556 throw new YangParseException(module.getName(), line,
557 "Error in augment parsing: failed to find node " + currentName + ".");
561 val oldPath = currentParent.path;
563 if (!(currentParent instanceof AugmentationTarget)) {
564 throw new YangParseException(module.getName(), line,
565 "Target of type " + currentParent.class + " cannot be augmented.");
568 switch (currentParent) {
569 case (currentParent instanceof ContainerSchemaNodeImpl): {
571 // includes container, input and output statement
572 val c = currentParent as ContainerSchemaNodeImpl;
573 val cb = c.toBuilder();
574 fillAugmentTarget(augment, cb);
575 (cb as AugmentationTargetBuilder ).addAugmentation(augment);
578 case (currentParent instanceof ListSchemaNodeImpl): {
579 val l = currentParent as ListSchemaNodeImpl;
580 val lb = l.toBuilder();
581 fillAugmentTarget(augment, lb);
582 (lb as AugmentationTargetBuilder ).addAugmentation(augment);
584 augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
585 augment.setResolved(true);
587 case (currentParent instanceof ChoiceNodeImpl): {
588 val ch = currentParent as ChoiceNodeImpl;
589 val chb = ch.toBuilder();
590 fillAugmentTarget(augment, chb);
591 (chb as AugmentationTargetBuilder ).addAugmentation(augment);
593 augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
594 augment.setResolved(true);
596 case (currentParent instanceof ChoiceCaseNodeImpl): {
597 val chc = currentParent as ChoiceCaseNodeImpl;
598 val chcb = chc.toBuilder();
599 fillAugmentTarget(augment, chcb);
600 (chcb as AugmentationTargetBuilder ).addAugmentation(augment);
602 augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
603 augment.setResolved(true);
605 case (currentParent instanceof NotificationDefinitionImpl): {
606 val nd = currentParent as NotificationDefinitionImpl;
607 val nb = nd.toBuilder();
608 fillAugmentTarget(augment, nb);
609 (nb as AugmentationTargetBuilder ).addAugmentation(augment);
611 augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
612 augment.setResolved(true);
615 augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
616 augment.setResolved(true);
620 public static def IdentitySchemaNodeBuilder findBaseIdentity(Map<String, TreeMap<Date, ModuleBuilder>> modules,
621 ModuleBuilder module, String baseString, int line) {
622 var IdentitySchemaNodeBuilder result = null;
623 if (baseString.contains(":")) {
624 val String[] splittedBase = baseString.split(":");
625 if (splittedBase.length > 2) {
626 throw new YangParseException(module.getName(), line, "Failed to parse identityref base: " +
629 val prefix = splittedBase.get(0);
630 val name = splittedBase.get(1);
631 val dependentModule = findModuleFromBuilders(modules, module, prefix, line);
632 if (dependentModule !== null) {
633 result = findIdentity(dependentModule.identities, name);
636 result = findIdentity(module.identities, baseString);
641 public static def IdentitySchemaNode findBaseIdentityFromContext(Map<String, TreeMap<Date, ModuleBuilder>> modules,
642 ModuleBuilder module, String baseString, int line, SchemaContext context) {
643 var IdentitySchemaNode result = null;
645 val String[] splittedBase = baseString.split(":");
646 if (splittedBase.length > 2) {
647 throw new YangParseException(module.getName(), line, "Failed to parse identityref base: " + baseString);
649 val prefix = splittedBase.get(0);
650 val name = splittedBase.get(1);
651 val dependentModule = findModuleFromContext(context, module, prefix, line);
652 result = findIdentityNode(dependentModule.identities, name);
654 if (result == null) {
655 throw new YangParseException(module.name, line, "Failed to find base identity");
660 private static def IdentitySchemaNodeBuilder findIdentity(Set<IdentitySchemaNodeBuilder> identities, String name) {
661 for (identity : identities) {
662 if (identity.QName.localName.equals(name)) {
669 private static def IdentitySchemaNode findIdentityNode(Set<IdentitySchemaNode> identities, String name) {
670 for (identity : identities) {
671 if (identity.QName.localName.equals(name)) {
679 * Get module in which this node is defined.
682 * @return builder of module where this node is defined
684 public static def ModuleBuilder getParentModule(Builder node) {
685 if (node instanceof ModuleBuilder) {
686 return node as ModuleBuilder;
688 var parent = node.getParent();
689 while (!(parent instanceof ModuleBuilder)) {
690 parent = parent.getParent();
692 return parent as ModuleBuilder;