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;
15 import java.util.TreeMap;
17 import org.opendaylight.yangtools.yang.common.QName;
18 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
19 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
20 import org.opendaylight.yangtools.yang.model.api.Module;
21 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
22 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
23 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
24 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
25 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
26 import org.opendaylight.yangtools.yang.parser.builder.api.AugmentationSchemaBuilder;
27 import org.opendaylight.yangtools.yang.parser.builder.api.AugmentationTargetBuilder;
28 import org.opendaylight.yangtools.yang.parser.builder.api.Builder;
29 import org.opendaylight.yangtools.yang.parser.builder.api.DataNodeContainerBuilder;
30 import org.opendaylight.yangtools.yang.parser.builder.api.DataSchemaNodeBuilder;
31 import org.opendaylight.yangtools.yang.parser.builder.api.SchemaNodeBuilder;
32 import org.opendaylight.yangtools.yang.parser.builder.api.UsesNodeBuilder;
33 import org.opendaylight.yangtools.yang.parser.builder.impl.ChoiceBuilder;
34 import org.opendaylight.yangtools.yang.parser.builder.impl.ChoiceBuilder.ChoiceNodeImpl;
35 import org.opendaylight.yangtools.yang.parser.builder.impl.ChoiceCaseBuilder;
36 import org.opendaylight.yangtools.yang.parser.builder.impl.ChoiceCaseBuilder.ChoiceCaseNodeImpl;
37 import org.opendaylight.yangtools.yang.parser.builder.impl.ContainerSchemaNodeBuilder.ContainerSchemaNodeImpl;
38 import org.opendaylight.yangtools.yang.parser.builder.impl.IdentityrefTypeBuilder;
39 import org.opendaylight.yangtools.yang.parser.builder.impl.ListSchemaNodeBuilder.ListSchemaNodeImpl;
40 import org.opendaylight.yangtools.yang.parser.builder.impl.ModuleBuilder;
41 import org.opendaylight.yangtools.yang.parser.builder.impl.NotificationBuilder.NotificationDefinitionImpl;
42 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget
44 public final class ParserUtils {
50 * Create new SchemaPath from given path and qname.
54 * @return new SchemaPath from given path and qname
56 public static def SchemaPath createSchemaPath(SchemaPath schemaPath, QName... qname) {
57 val path = new ArrayList<QName>(schemaPath.getPath());
58 path.addAll(Arrays.asList(qname));
59 return new SchemaPath(path, schemaPath.isAbsolute());
63 * Get module import referenced by given prefix.
68 * prefix associated with import
69 * @return ModuleImport based on given prefix
71 public static def ModuleImport getModuleImport(ModuleBuilder builder, String prefix) {
72 for (ModuleImport mi : builder.getModuleImports()) {
73 if (mi.getPrefix().equals(prefix)) {
82 * Find dependent module based on given prefix
85 * all available modules
89 * target module prefix
91 * current line in yang model
92 * @return module builder if found, null otherwise
94 public static def ModuleBuilder findModuleFromBuilders(Map<String, TreeMap<Date, ModuleBuilder>> modules,
95 ModuleBuilder module, String prefix, int line) {
96 var ModuleBuilder dependentModule = null;
97 var Date dependentModuleRevision = null;
99 if (prefix.equals(module.getPrefix())) {
100 dependentModule = module;
102 val ModuleImport dependentModuleImport = getModuleImport(module, prefix);
103 if (dependentModuleImport === null) {
104 throw new YangParseException(module.getName(), line, "No import found with prefix '" + prefix + "'.");
106 val String dependentModuleName = dependentModuleImport.getModuleName();
107 dependentModuleRevision = dependentModuleImport.getRevision();
109 val TreeMap<Date, ModuleBuilder> moduleBuildersByRevision = modules.get(dependentModuleName);
110 if (moduleBuildersByRevision === null) {
113 if (dependentModuleRevision === null) {
114 dependentModule = moduleBuildersByRevision.lastEntry().getValue();
116 dependentModule = moduleBuildersByRevision.get(dependentModuleRevision);
119 return dependentModule;
123 * Find module from context based on prefix.
127 * @param currentModule
130 * current prefix used to reference dependent module
132 * current line in yang model
133 * @return module based on given prefix if found in context, null otherwise
135 public static def Module findModuleFromContext(SchemaContext context, ModuleBuilder currentModule,
136 String prefix, int line) {
137 if (context === null) {
138 throw new YangParseException(currentModule.getName(), line, "Cannot find module with prefix '" + prefix + "'.");
140 val modulesByRevision = new TreeMap<Date, Module>();
142 val dependentModuleImport = ParserUtils.getModuleImport(currentModule, prefix);
143 if (dependentModuleImport === null) {
144 throw new YangParseException(currentModule.getName(), line, "No import found with prefix '" + prefix + "'.");
146 val dependentModuleName = dependentModuleImport.getModuleName();
147 val dependentModuleRevision = dependentModuleImport.getRevision();
149 for (Module contextModule : context.getModules()) {
150 if (contextModule.getName().equals(dependentModuleName)) {
151 var revision = contextModule.getRevision();
152 if (revision === null) {
153 revision = new Date(0L);
155 modulesByRevision.put(revision, contextModule);
159 var Module result = null;
160 if (dependentModuleRevision === null) {
161 result = modulesByRevision.get(modulesByRevision.firstKey());
163 result = modulesByRevision.get(dependentModuleRevision);
169 * Parse XPath string.
173 * @return SchemaPath from given String
175 public static def SchemaPath parseXPathString(String xpathString) {
176 val absolute = xpathString.startsWith("/");
177 val String[] splittedPath = xpathString.split("/");
178 val path = new ArrayList<QName>();
180 for (String pathElement : splittedPath) {
181 if (pathElement.length() > 0) {
182 val String[] splittedElement = pathElement.split(":");
183 if (splittedElement.length == 1) {
184 name = new QName(null, null, null, splittedElement.get(0));
186 name = new QName(null, null, splittedElement.get(0), splittedElement.get(1));
191 return new SchemaPath(path, absolute);
195 * Add all augment's child nodes to given target.
198 * builder of augment statement
200 * augmentation target node
202 public static def void fillAugmentTarget(AugmentationSchemaBuilder augment, DataNodeContainerBuilder target) {
203 for (DataSchemaNodeBuilder child : augment.getChildNodeBuilders()) {
204 val childCopy = CopyUtils.copy(child, target, false);
205 setNodeAugmenting(childCopy, augment);
206 correctNodePath(child, target.getPath());
207 correctNodePath(childCopy, target.getPath());
209 target.addChildNode(childCopy);
210 } catch (YangParseException e) {
212 // more descriptive message
213 throw new YangParseException(augment.getModuleName(), augment.getLine(),
214 "Failed to perform augmentation: " + e.getMessage());
217 for (UsesNodeBuilder usesNode : augment.getUsesNodes()) {
218 val copy = CopyUtils.copyUses(usesNode, target);
219 target.addUsesNode(copy);
223 private static def void setNodeAugmenting(DataSchemaNodeBuilder child, AugmentationSchemaBuilder augment) {
224 child.setAugmenting(true);
225 if (child instanceof DataNodeContainerBuilder) {
226 val DataNodeContainerBuilder dataNodeChild = child as DataNodeContainerBuilder;
227 for (inner : dataNodeChild.getChildNodeBuilders()) {
228 setNodeAugmenting(inner, augment);
230 for (uses : dataNodeChild.getUsesNodes()) {
231 uses.setParentAugment(augment);
232 uses.setAugmenting(true);
238 * Add all augment's child nodes to given target.
241 * builder of augment statement
243 * augmentation target choice node
245 public static def void fillAugmentTarget(AugmentationSchemaBuilder augment, ChoiceBuilder target) {
246 for (DataSchemaNodeBuilder builder : augment.getChildNodeBuilders()) {
247 val childCopy = CopyUtils.copy(builder, target, false);
248 childCopy.setAugmenting(true);
249 correctNodePath(builder, target.getPath());
250 correctNodePath(childCopy, target.getPath());
251 target.addCase(childCopy);
253 for (UsesNodeBuilder usesNode : augment.getUsesNodes()) {
254 if (usesNode !== null) {
255 throw new YangParseException(augment.getModuleName(), augment.getLine(),
256 "Error in augment parsing: cannot augment uses to choice");
262 * Create new schema path of node based on parent node schema path.
266 * @param parentSchemaPath
267 * schema path of node parent
269 static def void correctNodePath(SchemaNodeBuilder node, SchemaPath parentSchemaPath) {
272 val targetNodePath = new ArrayList<QName>(parentSchemaPath.getPath());
273 targetNodePath.add(node.getQName());
274 node.setPath(new SchemaPath(targetNodePath, true));
276 // set correct path for all child nodes
277 if (node instanceof DataNodeContainerBuilder) {
278 val dataNodeContainer = node as DataNodeContainerBuilder;
279 for (DataSchemaNodeBuilder child : dataNodeContainer.getChildNodeBuilders()) {
280 correctNodePath(child, node.getPath());
284 // set correct path for all cases
285 if (node instanceof ChoiceBuilder) {
286 val choiceBuilder = node as ChoiceBuilder;
287 for (ChoiceCaseBuilder choiceCaseBuilder : choiceBuilder.getCases()) {
288 correctNodePath(choiceCaseBuilder, node.getPath());
295 private static def Builder findNode(Builder firstNodeParent, List<QName> path, String moduleName, int line) {
296 var currentName = "";
297 var currentParent = firstNodeParent;
299 val max = path.size();
302 var qname = path.get(i);
304 currentName = qname.getLocalName();
305 if (currentParent instanceof DataNodeContainerBuilder) {
306 var dataNodeContainerParent = currentParent as DataNodeContainerBuilder;
307 var SchemaNodeBuilder nodeFound = dataNodeContainerParent.getDataChildByName(currentName);
308 // if not found, search in notifications
309 if (nodeFound == null && currentParent instanceof ModuleBuilder) {
310 nodeFound = searchNotifications(currentParent as ModuleBuilder, currentName);
312 // if not found, search in uses
313 if (nodeFound == null) {
314 var found = searchUses(dataNodeContainerParent, currentName);
318 currentParent = found;
321 currentParent = nodeFound;
323 } else if (currentParent instanceof ChoiceBuilder) {
324 val choiceParent = currentParent as ChoiceBuilder;
325 currentParent = choiceParent.getCaseNodeByName(currentName);
327 throw new YangParseException(moduleName, line,
328 "Error in augment parsing: failed to find node " + currentName);
331 // if node in path not found, return null
332 if (currentParent == null) {
337 return currentParent;
340 private static def searchNotifications(ModuleBuilder parent, String name) {
341 for(notification : parent.notifications) {
342 if(notification.getQName().localName.equals(name)) {
349 private static def searchUses(DataNodeContainerBuilder dataNodeContainerParent, String name) {
350 var currentName = name;
351 for (unb : dataNodeContainerParent.usesNodes) {
352 val result = findNodeInUses(currentName, unb);
353 if (result != null) {
354 var copy = CopyUtils.copy(result, unb.getParent(), true);
355 unb.getTargetChildren().add(copy);
362 public static def getRpc(ModuleBuilder module,String name) {
363 for(rpc : module.rpcs) {
364 if(name == rpc.QName.localName) {
371 public static def getNotification(ModuleBuilder module,String name) {
372 for(notification : module.notifications) {
373 if(name == notification.QName.localName) {
379 private static def nextLevel(List<QName> path){
380 return path.subList(1,path.size)
384 * Find augment target node and perform augmentation.
387 * @param firstNodeParent
388 * parent of first node in path
390 * path to augment target
391 * @return true if augmentation process succeed, false otherwise
393 public static def boolean processAugmentation(AugmentationSchemaBuilder augment, Builder firstNodeParent,
396 // traverse augment target path and try to reach target node
397 val targetNode = findNode(firstNodeParent,path,augment.moduleName,augment.line);
398 if (targetNode === null) return false;
400 if ((targetNode instanceof DataNodeContainerBuilder)) {
401 val targetDataNodeContainer = targetNode as DataNodeContainerBuilder;
402 augment.setTargetNodeSchemaPath(targetDataNodeContainer.getPath());
403 fillAugmentTarget(augment, targetDataNodeContainer);
404 } else if (targetNode instanceof ChoiceBuilder) {
405 val targetChoiceBuilder = targetNode as ChoiceBuilder;
406 augment.setTargetNodeSchemaPath(targetChoiceBuilder.getPath());
407 fillAugmentTarget(augment, targetChoiceBuilder);
409 throw new YangParseException(augment.getModuleName(), augment.getLine(),
410 "Error in augment parsing: The target node MUST be either a container, list, choice, case, input, output, or notification node.");
412 (targetNode as AugmentationTargetBuilder).addAugmentation(augment);
413 augment.setResolved(true);
418 * Find node with given name in uses target.
421 * name of node to find
423 * uses node which target grouping should be searched
424 * @return node with given name if found, null otherwise
426 private static def DataSchemaNodeBuilder findNodeInUses(String localName, UsesNodeBuilder uses) {
427 for(child : uses.targetChildren) {
428 if (child.getQName().getLocalName().equals(localName)) {
433 val target = uses.groupingBuilder;
434 for (child : target.childNodeBuilders) {
435 if (child.getQName().getLocalName().equals(localName)) {
439 for (usesNode : target.usesNodes) {
440 val result = findNodeInUses(localName, usesNode);
441 if (result != null) {
449 * Find augment target node in given context and perform augmentation.
453 * path to augment target
457 * current prefix of target module
459 * SchemaContext containing already resolved modules
460 * @return true if augment process succeed, false otherwise
462 public static def boolean processAugmentationOnContext(AugmentationSchemaBuilder augment, List<QName> path,
463 ModuleBuilder module, String prefix, SchemaContext context) {
464 val int line = augment.getLine();
465 val Module dependentModule = findModuleFromContext(context, module, prefix, line);
466 if (dependentModule === null) {
467 throw new YangParseException(module.getName(), line,
468 "Error in augment parsing: failed to find module with prefix " + prefix + ".");
471 var currentName = path.get(0).getLocalName();
472 var SchemaNode currentParent = dependentModule.getDataChildByName(currentName);
473 if (currentParent === null) {
474 val notifications = dependentModule.getNotifications();
475 for (NotificationDefinition ntf : notifications) {
476 if (ntf.getQName().getLocalName().equals(currentName)) {
481 if (currentParent === null) {
482 throw new YangParseException(module.getName(), line,
483 "Error in augment parsing: failed to find node " + currentName + ".");
486 for (qname : path.nextLevel) {
487 currentName = qname.getLocalName();
488 if (currentParent instanceof DataNodeContainer) {
489 currentParent = (currentParent as DataNodeContainer).getDataChildByName(currentName);
490 } else if (currentParent instanceof ChoiceNode) {
491 currentParent = (currentParent as ChoiceNode).getCaseNodeByName(currentName);
493 throw new YangParseException(augment.getModuleName(), line,
494 "Error in augment parsing: failed to find node " + currentName);
497 // if node in path not found, return false
498 if (currentParent === null) {
499 throw new YangParseException(module.getName(), line,
500 "Error in augment parsing: failed to find node " + currentName + ".");
504 val oldPath = currentParent.path;
506 if (!(currentParent instanceof AugmentationTarget)) {
507 throw new YangParseException(module.getName(), line,
508 "Target of type " + currentParent.class + " cannot be augmented.");
511 switch (currentParent) {
512 case (currentParent instanceof ContainerSchemaNodeImpl): {
514 // includes container, input and output statement
515 val c = currentParent as ContainerSchemaNodeImpl;
516 val cb = c.toBuilder();
517 fillAugmentTarget(augment, cb);
518 (cb as AugmentationTargetBuilder ).addAugmentation(augment);
521 case (currentParent instanceof ListSchemaNodeImpl): {
522 val l = currentParent as ListSchemaNodeImpl;
523 val lb = l.toBuilder();
524 fillAugmentTarget(augment, lb);
525 (lb as AugmentationTargetBuilder ).addAugmentation(augment);
527 augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
528 augment.setResolved(true);
530 case (currentParent instanceof ChoiceNodeImpl): {
531 val ch = currentParent as ChoiceNodeImpl;
532 val chb = ch.toBuilder();
533 fillAugmentTarget(augment, chb);
534 (chb as AugmentationTargetBuilder ).addAugmentation(augment);
536 augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
537 augment.setResolved(true);
539 case (currentParent instanceof ChoiceCaseNodeImpl): {
540 val chc = currentParent as ChoiceCaseNodeImpl;
541 val chcb = chc.toBuilder();
542 fillAugmentTarget(augment, chcb);
543 (chcb as AugmentationTargetBuilder ).addAugmentation(augment);
545 augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
546 augment.setResolved(true);
548 case (currentParent instanceof NotificationDefinitionImpl): {
549 val nd = currentParent as NotificationDefinitionImpl;
550 val nb = nd.toBuilder();
551 fillAugmentTarget(augment, nb);
552 (nb as AugmentationTargetBuilder ).addAugmentation(augment);
554 augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
555 augment.setResolved(true);
558 augment.setTargetNodeSchemaPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
559 augment.setResolved(true);
563 public static def QName findFullQName(Map<String, TreeMap<Date, ModuleBuilder>> modules,
564 ModuleBuilder module, IdentityrefTypeBuilder idref) {
565 var QName result = null;
566 val String baseString = idref.getBaseString();
567 if (baseString.contains(":")) {
568 val String[] splittedBase = baseString.split(":");
569 if (splittedBase.length > 2) {
570 throw new YangParseException(module.getName(), idref.getLine(),
571 "Failed to parse identityref base: " + baseString);
573 val prefix = splittedBase.get(0);
574 val name = splittedBase.get(1);
575 val dependentModule = findModuleFromBuilders(modules, module, prefix, idref.getLine());
576 result = new QName(dependentModule.getNamespace(), dependentModule.getRevision(), prefix, name);
578 result = new QName(module.getNamespace(), module.getRevision(), module.getPrefix(), baseString);
584 * Get module in which this node is defined.
587 * @return builder of module where this node is defined
589 public static def ModuleBuilder getParentModule(Builder node) {
590 if (node instanceof ModuleBuilder) {
591 return node as ModuleBuilder;
593 var parent = node.getParent();
594 while (!(parent instanceof ModuleBuilder)) {
595 parent = parent.getParent();
597 return parent as ModuleBuilder;