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
45 public final class ParserUtils {
51 * Create new SchemaPath from given path and qname.
57 public static def SchemaPath createSchemaPath(SchemaPath schemaPath, QName... qname) {
58 val path = new ArrayList<QName>(schemaPath.getPath());
59 path.addAll(Arrays.asList(qname));
60 return new SchemaPath(path, schemaPath.isAbsolute());
64 * Get module import referenced by given prefix.
69 * prefix associated with import
70 * @return ModuleImport based on given prefix
72 public static def ModuleImport getModuleImport(ModuleBuilder builder, String prefix) {
73 for (ModuleImport mi : builder.getModuleImports()) {
74 if (mi.getPrefix().equals(prefix)) {
83 * Find dependent module based on given prefix
86 * all available modules
90 * target module prefix
92 * current line in yang model
93 * @return module builder if found, null otherwise
95 public static def ModuleBuilder findDependentModuleBuilder(Map<String, TreeMap<Date, ModuleBuilder>> modules,
96 ModuleBuilder module, String prefix, int line) {
97 var ModuleBuilder dependentModule = null;
98 var Date dependentModuleRevision = null;
100 if (prefix.equals(module.getPrefix())) {
101 dependentModule = module;
103 val ModuleImport dependentModuleImport = getModuleImport(module, prefix);
104 if (dependentModuleImport === null) {
105 throw new YangParseException(module.getName(), line, "No import found with prefix '" + prefix + "'.");
107 val String dependentModuleName = dependentModuleImport.getModuleName();
108 dependentModuleRevision = dependentModuleImport.getRevision();
110 val TreeMap<Date, ModuleBuilder> moduleBuildersByRevision = modules.get(dependentModuleName);
111 if (moduleBuildersByRevision === null) {
114 if (dependentModuleRevision === null) {
115 dependentModule = moduleBuildersByRevision.lastEntry().getValue();
117 dependentModule = moduleBuildersByRevision.get(dependentModuleRevision);
120 return dependentModule;
124 * Find module from context based on prefix.
128 * @param currentModule
131 * current prefix used to reference dependent module
133 * current line in yang model
134 * @return module based on given prefix if found in context, null otherwise
136 public static def Module findModuleFromContext(SchemaContext context, ModuleBuilder currentModule,
137 String prefix, int line) {
138 val modulesByRevision = new TreeMap<Date, Module>();
140 val dependentModuleImport = ParserUtils.getModuleImport(currentModule, prefix);
141 if (dependentModuleImport === null) {
142 throw new YangParseException(currentModule.getName(), line, "No import found with prefix '" + prefix + "'.");
144 val dependentModuleName = dependentModuleImport.getModuleName();
145 val dependentModuleRevision = dependentModuleImport.getRevision();
147 for (Module contextModule : context.getModules()) {
148 if (contextModule.getName().equals(dependentModuleName)) {
149 var revision = contextModule.getRevision();
150 if (revision === null) {
151 revision = new Date(0L);
153 modulesByRevision.put(revision, contextModule);
157 var Module result = null;
158 if (dependentModuleRevision === null) {
159 result = modulesByRevision.get(modulesByRevision.firstKey());
161 result = modulesByRevision.get(dependentModuleRevision);
167 * Parse XPath string.
171 * @return SchemaPath from given String
173 public static def SchemaPath parseXPathString(String xpathString) {
174 val absolute = xpathString.startsWith("/");
175 val String[] splittedPath = xpathString.split("/");
176 val path = new ArrayList<QName>();
178 for (String pathElement : splittedPath) {
179 if (pathElement.length() > 0) {
180 val String[] splittedElement = pathElement.split(":");
181 if (splittedElement.length == 1) {
182 name = new QName(null, null, null, splittedElement.get(0));
184 name = new QName(null, null, splittedElement.get(0), splittedElement.get(1));
189 return new SchemaPath(path, absolute);
193 * Add all augment's child nodes to given target.
196 * builder of augment statement
198 * augmentation target node
200 public static def void fillAugmentTarget(AugmentationSchemaBuilder augment, DataNodeContainerBuilder target) {
201 for (DataSchemaNodeBuilder child : augment.getChildNodeBuilders()) {
202 val childCopy = CopyUtils.copy(child, target, false);
203 childCopy.setAugmenting(true);
204 correctNodePath(child, target.getPath());
205 correctNodePath(childCopy, target.getPath());
207 target.addChildNode(childCopy);
208 } catch (YangParseException e) {
210 // more descriptive message
211 throw new YangParseException(augment.getModuleName(), augment.getLine(),
212 "Failed to perform augmentation: " + e.getMessage());
216 for (UsesNodeBuilder usesNode : augment.getUsesNodes()) {
217 val copy = CopyUtils.copyUses(usesNode, target);
218 target.addUsesNode(copy);
223 * Add all augment's child nodes to given target.
226 * builder of augment statement
228 * augmentation target choice node
230 public static def void fillAugmentTarget(AugmentationSchemaBuilder augment, ChoiceBuilder target) {
231 for (DataSchemaNodeBuilder builder : augment.getChildNodeBuilders()) {
232 val childCopy = CopyUtils.copy(builder, target, false);
233 childCopy.setAugmenting(true);
234 correctNodePath(builder, target.getPath());
235 correctNodePath(childCopy, target.getPath());
236 target.addCase(childCopy);
238 for (UsesNodeBuilder usesNode : augment.getUsesNodes()) {
239 if (usesNode !== null) {
240 throw new YangParseException(augment.getModuleName(), augment.getLine(),
241 "Error in augment parsing: cannot augment uses to choice");
247 * Create new schema path of node based on parent node schema path.
251 * @param parentSchemaPath
252 * schema path of node parent
254 static def void correctNodePath(SchemaNodeBuilder node, SchemaPath parentSchemaPath) {
257 val targetNodePath = new ArrayList<QName>(parentSchemaPath.getPath());
258 targetNodePath.add(node.getQName());
259 node.setPath(new SchemaPath(targetNodePath, true));
261 // set correct path for all child nodes
262 if (node instanceof DataNodeContainerBuilder) {
263 val dataNodeContainer = node as DataNodeContainerBuilder;
264 for (DataSchemaNodeBuilder child : dataNodeContainer.getChildNodeBuilders()) {
265 correctNodePath(child, node.getPath());
269 // set correct path for all cases
270 if (node instanceof ChoiceBuilder) {
271 val choiceBuilder = node as ChoiceBuilder;
272 for (ChoiceCaseBuilder choiceCaseBuilder : choiceBuilder.getCases()) {
273 correctNodePath(choiceCaseBuilder, node.getPath());
280 private static def Builder findNode(Builder firstNodeParent, List<QName> path, String moduleName, int line) {
281 var currentName = "";
282 var currentParent = firstNodeParent;
284 val max = path.size();
287 var qname = path.get(i);
289 currentName = qname.getLocalName();
290 if (currentParent instanceof DataNodeContainerBuilder) {
291 var dataNodeContainerParent = currentParent as DataNodeContainerBuilder;
292 var SchemaNodeBuilder nodeFound = dataNodeContainerParent.getDataChildByName(currentName);
293 if (nodeFound == null && currentParent instanceof ModuleBuilder) {
294 nodeFound = searchNotifications(currentParent as ModuleBuilder, currentName);
296 // if not found as regular child, search in uses
297 if (nodeFound == null) {
298 var found = searchUses(dataNodeContainerParent, currentName);
302 currentParent = found;
305 currentParent = nodeFound;
307 } else if (currentParent instanceof ChoiceBuilder) {
308 val choiceParent = currentParent as ChoiceBuilder;
309 currentParent = choiceParent.getCaseNodeByName(currentName);
311 throw new YangParseException(moduleName, line,
312 "Error in augment parsing: failed to find node " + currentName);
315 // if node in path not found, return null
316 if (currentParent == null) {
321 return currentParent;
324 private static def searchNotifications(ModuleBuilder parent, String name) {
325 for(notification : parent.notifications) {
326 if(notification.getQName().localName.equals(name)) {
333 private static def searchUses(DataNodeContainerBuilder dataNodeContainerParent, String name) {
334 var currentName = name;
335 for (unb : dataNodeContainerParent.usesNodes) {
336 val result = findNodeInUses(currentName, unb);
337 if (result != null) {
338 var copy = CopyUtils.copy(result, unb.getParent(), true);
339 unb.getTargetChildren().add(copy);
346 public static def getRpc(ModuleBuilder module,String name) {
347 for(rpc : module.rpcs) {
348 if(name == rpc.QName.localName) {
355 public static def getNotification(ModuleBuilder module,String name) {
356 for(notification : module.notifications) {
357 if(name == notification.QName.localName) {
363 private static def nextLevel(List<QName> path){
364 return path.subList(1,path.size)
368 * Find augment target node and perform augmentation.
371 * @param firstNodeParent
372 * parent of first node in path
374 * path to augment target
375 * @return true if augmentation process succeed, false otherwise
377 public static def boolean processAugmentation(AugmentationSchemaBuilder augment, Builder firstNodeParent,
380 // traverse augment target path and try to reach target node
381 val currentParent = findNode(firstNodeParent,path,augment.moduleName,augment.line);
382 if (currentParent === null) return false;
384 if ((currentParent instanceof DataNodeContainerBuilder)) {
385 fillAugmentTarget(augment, currentParent as DataNodeContainerBuilder);
386 } else if (currentParent instanceof ChoiceBuilder) {
387 fillAugmentTarget(augment, currentParent as ChoiceBuilder);
389 throw new YangParseException(augment.getModuleName(), augment.getLine(),
390 "Error in augment parsing: The target node MUST be either a container, list, choice, case, input, output, or notification node.");
392 (currentParent as AugmentationTargetBuilder).addAugmentation(augment);
393 augment.setResolved(true);
398 * Find node with given name in uses target.
401 * name of node to find
403 * uses node which target grouping should be searched
404 * @return node with given name if found, null otherwise
406 private static def DataSchemaNodeBuilder findNodeInUses(String localName, UsesNodeBuilder uses) {
407 for(child : uses.targetChildren) {
408 if (child.getQName().getLocalName().equals(localName)) {
413 val target = uses.groupingBuilder;
414 for (child : target.childNodeBuilders) {
415 if (child.getQName().getLocalName().equals(localName)) {
419 for (usesNode : target.usesNodes) {
420 val result = findNodeInUses(localName, usesNode);
421 if (result != null) {
429 * Find augment target node in given context and perform augmentation.
433 * path to augment target
437 * current prefix of target module
439 * SchemaContext containing already resolved modules
440 * @return true if augment process succeed, false otherwise
442 public static def boolean processAugmentationOnContext(AugmentationSchemaBuilder augment, List<QName> path,
443 ModuleBuilder module, String prefix, SchemaContext context) {
444 val int line = augment.getLine();
445 val Module dependentModule = findModuleFromContext(context, module, prefix, line);
446 if (dependentModule === null) {
447 throw new YangParseException(module.getName(), line,
448 "Error in augment parsing: failed to find module with prefix " + prefix + ".");
451 var currentName = path.get(0).getLocalName();
452 var SchemaNode currentParent = dependentModule.getDataChildByName(currentName);
453 if (currentParent === null) {
454 val notifications = dependentModule.getNotifications();
455 for (NotificationDefinition ntf : notifications) {
456 if (ntf.getQName().getLocalName().equals(currentName)) {
461 if (currentParent === null) {
462 throw new YangParseException(module.getName(), line,
463 "Error in augment parsing: failed to find node " + currentName + ".");
466 for (qname : path.nextLevel) {
467 currentName = qname.getLocalName();
468 if (currentParent instanceof DataNodeContainer) {
469 currentParent = (currentParent as DataNodeContainer).getDataChildByName(currentName);
470 } else if (currentParent instanceof ChoiceNode) {
471 currentParent = (currentParent as ChoiceNode).getCaseNodeByName(currentName);
473 throw new YangParseException(augment.getModuleName(), line,
474 "Error in augment parsing: failed to find node " + currentName);
477 // if node in path not found, return false
478 if (currentParent === null) {
479 throw new YangParseException(module.getName(), line,
480 "Error in augment parsing: failed to find node " + currentName + ".");
484 val oldPath = currentParent.path;
486 if (!(currentParent instanceof AugmentationTarget)) {
487 throw new YangParseException(module.getName(), line,
488 "Target of type " + currentParent.class + " cannot be augmented.");
491 switch (currentParent) {
492 case (currentParent instanceof ContainerSchemaNodeImpl): {
494 // includes container, input and output statement
495 val c = currentParent as ContainerSchemaNodeImpl;
496 val cb = c.toBuilder();
497 fillAugmentTarget(augment, cb);
498 (cb as AugmentationTargetBuilder ).addAugmentation(augment);
501 case (currentParent instanceof ListSchemaNodeImpl): {
502 val l = currentParent as ListSchemaNodeImpl;
503 val lb = l.toBuilder();
504 fillAugmentTarget(augment, lb);
505 (lb as AugmentationTargetBuilder ).addAugmentation(augment);
507 augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
508 augment.setResolved(true);
510 case (currentParent instanceof ChoiceNodeImpl): {
511 val ch = currentParent as ChoiceNodeImpl;
512 val chb = ch.toBuilder();
513 fillAugmentTarget(augment, chb);
514 (chb as AugmentationTargetBuilder ).addAugmentation(augment);
516 augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
517 augment.setResolved(true);
519 case (currentParent instanceof ChoiceCaseNodeImpl): {
520 val chc = currentParent as ChoiceCaseNodeImpl;
521 val chcb = chc.toBuilder();
522 fillAugmentTarget(augment, chcb);
523 (chcb as AugmentationTargetBuilder ).addAugmentation(augment);
525 augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
526 augment.setResolved(true);
528 case (currentParent instanceof NotificationDefinitionImpl): {
529 val nd = currentParent as NotificationDefinitionImpl;
530 val nb = nd.toBuilder();
531 fillAugmentTarget(augment, nb);
532 (nb as AugmentationTargetBuilder ).addAugmentation(augment);
534 augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
535 augment.setResolved(true);
538 augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
539 augment.setResolved(true);
543 public static def QName findFullQName(Map<String, TreeMap<Date, ModuleBuilder>> modules,
544 ModuleBuilder module, IdentityrefTypeBuilder idref) {
545 var QName result = null;
546 val String baseString = idref.getBaseString();
547 if (baseString.contains(":")) {
548 val String[] splittedBase = baseString.split(":");
549 if (splittedBase.length > 2) {
550 throw new YangParseException(module.getName(), idref.getLine(),
551 "Failed to parse identityref base: " + baseString);
553 val prefix = splittedBase.get(0);
554 val name = splittedBase.get(1);
555 val dependentModule = findDependentModuleBuilder(modules, module, prefix, idref.getLine());
556 result = new QName(dependentModule.getNamespace(), dependentModule.getRevision(), prefix, name);
558 result = new QName(module.getNamespace(), module.getRevision(), module.getPrefix(), baseString);
564 * Get module in which this node is defined.
567 * @return builder of module where this node is defined
569 public static def ModuleBuilder getParentModule(Builder node) {
570 if (node instanceof ModuleBuilder) {
571 return node as ModuleBuilder;
573 var parent = node.getParent();
574 while (!(parent instanceof ModuleBuilder)) {
575 parent = parent.getParent();
577 return parent as ModuleBuilder;