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
43 import org.opendaylight.yangtools.yang.parser.builder.impl.RpcDefinitionBuilder
46 public final class ParserUtils {
52 * Create new SchemaPath from given path and qname.
58 public static def SchemaPath createSchemaPath(SchemaPath schemaPath, QName... qname) {
59 val path = new ArrayList<QName>(schemaPath.getPath());
60 path.addAll(Arrays.asList(qname));
61 return new SchemaPath(path, schemaPath.isAbsolute());
65 * Get module import referenced by given prefix.
70 * prefix associated with import
71 * @return ModuleImport based on given prefix
73 public static def ModuleImport getModuleImport(ModuleBuilder builder, String prefix) {
74 for (ModuleImport mi : builder.getModuleImports()) {
75 if (mi.getPrefix().equals(prefix)) {
84 * Find dependent module based on given prefix
87 * all available modules
91 * target module prefix
93 * current line in yang model
94 * @return module builder if found, null otherwise
96 public static def ModuleBuilder findDependentModuleBuilder(Map<String, TreeMap<Date, ModuleBuilder>> modules,
97 ModuleBuilder module, String prefix, int line) {
98 var ModuleBuilder dependentModule = null;
99 var Date dependentModuleRevision = null;
101 if (prefix.equals(module.getPrefix())) {
102 dependentModule = module;
104 val ModuleImport dependentModuleImport = getModuleImport(module, prefix);
105 if (dependentModuleImport === null) {
106 throw new YangParseException(module.getName(), line, "No import found with prefix '" + prefix + "'.");
108 val String dependentModuleName = dependentModuleImport.getModuleName();
109 dependentModuleRevision = dependentModuleImport.getRevision();
111 val TreeMap<Date, ModuleBuilder> moduleBuildersByRevision = modules.get(dependentModuleName);
112 if (moduleBuildersByRevision === null) {
115 if (dependentModuleRevision === null) {
116 dependentModule = moduleBuildersByRevision.lastEntry().getValue();
118 dependentModule = moduleBuildersByRevision.get(dependentModuleRevision);
121 return dependentModule;
125 * Find module from context based on prefix.
129 * @param currentModule
132 * current prefix used to reference dependent module
134 * current line in yang model
135 * @return module based on given prefix if found in context, null otherwise
137 public static def Module findModuleFromContext(SchemaContext context, ModuleBuilder currentModule,
138 String prefix, int line) {
139 val modulesByRevision = new TreeMap<Date, Module>();
141 val dependentModuleImport = ParserUtils.getModuleImport(currentModule, prefix);
142 if (dependentModuleImport === null) {
143 throw new YangParseException(currentModule.getName(), line, "No import found with prefix '" + prefix + "'.");
145 val dependentModuleName = dependentModuleImport.getModuleName();
146 val dependentModuleRevision = dependentModuleImport.getRevision();
148 for (Module contextModule : context.getModules()) {
149 if (contextModule.getName().equals(dependentModuleName)) {
150 var revision = contextModule.getRevision();
151 if (revision === null) {
152 revision = new Date(0L);
154 modulesByRevision.put(revision, contextModule);
158 var Module result = null;
159 if (dependentModuleRevision === null) {
160 result = modulesByRevision.get(modulesByRevision.firstKey());
162 result = modulesByRevision.get(dependentModuleRevision);
168 * Parse XPath string.
172 * @return SchemaPath from given String
174 public static def SchemaPath parseXPathString(String xpathString) {
175 val absolute = xpathString.startsWith("/");
176 val String[] splittedPath = xpathString.split("/");
177 val path = new ArrayList<QName>();
179 for (String pathElement : splittedPath) {
180 if (pathElement.length() > 0) {
181 val String[] splittedElement = pathElement.split(":");
182 if (splittedElement.length == 1) {
183 name = new QName(null, null, null, splittedElement.get(0));
185 name = new QName(null, null, splittedElement.get(0), splittedElement.get(1));
190 return new SchemaPath(path, absolute);
194 * Add all augment's child nodes to given target.
197 * builder of augment statement
199 * augmentation target node
201 public static def void fillAugmentTarget(AugmentationSchemaBuilder augment, DataNodeContainerBuilder target) {
202 for (DataSchemaNodeBuilder child : augment.getChildNodeBuilders()) {
203 val childCopy = CopyUtils.copy(child, target, false);
204 childCopy.setAugmenting(true);
205 correctNodePath(child, target.getPath());
206 correctNodePath(childCopy, target.getPath());
208 target.addChildNode(childCopy);
209 } catch (YangParseException e) {
211 // more descriptive message
212 throw new YangParseException(augment.getModuleName(), augment.getLine(),
213 "Failed to perform augmentation: " + e.getMessage());
217 for (UsesNodeBuilder usesNode : augment.getUsesNodes()) {
218 val copy = CopyUtils.copyUses(usesNode, target);
219 target.addUsesNode(copy);
224 * Add all augment's child nodes to given target.
227 * builder of augment statement
229 * augmentation target choice node
231 public static def void fillAugmentTarget(AugmentationSchemaBuilder augment, ChoiceBuilder target) {
232 for (DataSchemaNodeBuilder builder : augment.getChildNodeBuilders()) {
233 val childCopy = CopyUtils.copy(builder, target, false);
234 childCopy.setAugmenting(true);
235 correctNodePath(builder, target.getPath());
236 correctNodePath(childCopy, target.getPath());
237 target.addCase(childCopy);
239 for (UsesNodeBuilder usesNode : augment.getUsesNodes()) {
240 if (usesNode !== null) {
241 throw new YangParseException(augment.getModuleName(), augment.getLine(),
242 "Error in augment parsing: cannot augment uses to choice");
248 * Create new schema path of node based on parent node schema path.
252 * @param parentSchemaPath
253 * schema path of node parent
255 static def void correctNodePath(SchemaNodeBuilder node, SchemaPath parentSchemaPath) {
258 val targetNodePath = new ArrayList<QName>(parentSchemaPath.getPath());
259 targetNodePath.add(node.getQName());
260 node.setPath(new SchemaPath(targetNodePath, true));
262 // set correct path for all child nodes
263 if (node instanceof DataNodeContainerBuilder) {
264 val dataNodeContainer = node as DataNodeContainerBuilder;
265 for (DataSchemaNodeBuilder child : dataNodeContainer.getChildNodeBuilders()) {
266 correctNodePath(child, node.getPath());
270 // set correct path for all cases
271 if (node instanceof ChoiceBuilder) {
272 val choiceBuilder = node as ChoiceBuilder;
273 for (ChoiceCaseBuilder choiceCaseBuilder : choiceBuilder.getCases()) {
274 correctNodePath(choiceCaseBuilder, node.getPath());
281 public static dispatch def SchemaNodeBuilder findNode(ModuleBuilder parent,List<QName> path) {
282 var node = _findNode(parent as DataNodeContainerBuilder,path);
283 if(node !== null) return node;
285 val current = path.get(0);
286 node = parent.getRpc(current.localName);
287 if(node !== null) return _findNode(node as RpcDefinitionBuilder,path.nextLevel);
288 node = parent.getNotification(current.localName);
292 public static dispatch def SchemaNodeBuilder findNode(DataNodeContainerBuilder parent,List<QName> path) {
293 if(path.empty) return parent as SchemaNodeBuilder;
295 var current = path.get(0);
296 var node = parent.getDataChildByName(current.localName)
297 if(node !== null) return findNode(node,path.nextLevel);
298 for (UsesNodeBuilder unb : parent.usesNodes) {
299 node = findNodeInUses(current.localName, unb);
301 return findNode(node,path.nextLevel);
306 public static dispatch def SchemaNodeBuilder findNode(RpcDefinitionBuilder parent,List<QName> path) {
307 val current = path.get(0);
308 switch(current.localName) {
309 case "input": return findNode(parent.input,path.nextLevel)
310 case "output": return findNode(parent.output,path.nextLevel)
315 public static dispatch def SchemaNodeBuilder findNode(ChoiceBuilder parent,List<QName> path) {
316 if(path.empty) return parent as SchemaNodeBuilder;
317 var current = path.get(0);
318 val node = parent.getCaseNodeByName(current.localName);
319 if(node === null) return null;
320 return findNode(node,path.nextLevel);
323 public static def getRpc(ModuleBuilder module,String name) {
324 for(rpc : module.rpcs) {
325 if(name == rpc.QName.localName) {
332 public static def getNotification(ModuleBuilder module,String name) {
333 for(notification : module.notifications) {
334 if(name == notification.QName.localName) {
340 private static def nextLevel(List<QName> path){
341 return path.subList(1,path.size)
345 * Find augment target node and perform augmentation.
348 * @param firstNodeParent
349 * parent of first node in path
351 * path to augment target
352 * @return true if augmentation process succeed, false otherwise
354 public static def boolean processAugmentation(AugmentationSchemaBuilder augment, Builder firstNodeParent,
357 // traverse augment target path and try to reach target node
358 val currentParent = findNode(firstNodeParent,path);
359 if (currentParent === null) return false;
361 if ((currentParent instanceof DataNodeContainerBuilder)) {
362 fillAugmentTarget(augment, currentParent as DataNodeContainerBuilder);
363 } else if (currentParent instanceof ChoiceBuilder) {
364 fillAugmentTarget(augment, currentParent as ChoiceBuilder);
366 throw new YangParseException(augment.getModuleName(), augment.getLine(),
367 "Error in augment parsing: The target node MUST be either a container, list, choice, case, input, output, or notification node.");
369 (currentParent as AugmentationTargetBuilder).addAugmentation(augment);
370 val oldPath = (currentParent as SchemaNodeBuilder).getPath();
371 augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
372 augment.setResolved(true);
377 * Find node with given name in uses target.
380 * name of node to find
382 * uses node which target grouping should be searched
383 * @return node with given name if found, null otherwise
385 private static def DataSchemaNodeBuilder findNodeInUses(String localName, UsesNodeBuilder uses) {
386 val target = uses.getGroupingBuilder();
387 for (DataSchemaNodeBuilder child : target.getChildNodeBuilders()) {
388 if (child.getQName().getLocalName().equals(localName)) {
392 for (UsesNodeBuilder usesNode : target.getUsesNodes()) {
393 val result = findNodeInUses(localName, usesNode);
394 if (result !== null) {
402 * Find augment target node in given context and perform augmentation.
406 * path to augment target
410 * current prefix of target module
412 * SchemaContext containing already resolved modules
413 * @return true if augment process succeed, false otherwise
415 public static def boolean processAugmentationOnContext(AugmentationSchemaBuilder augment, List<QName> path,
416 ModuleBuilder module, String prefix, SchemaContext context) {
417 val int line = augment.getLine();
418 val Module dependentModule = findModuleFromContext(context, module, prefix, line);
419 if (dependentModule === null) {
420 throw new YangParseException(module.getName(), line,
421 "Error in augment parsing: failed to find module with prefix " + prefix + ".");
424 var currentName = path.get(0).getLocalName();
425 var SchemaNode currentParent = dependentModule.getDataChildByName(currentName);
426 if (currentParent === null) {
427 val notifications = dependentModule.getNotifications();
428 for (NotificationDefinition ntf : notifications) {
429 if (ntf.getQName().getLocalName().equals(currentName)) {
434 if (currentParent === null) {
435 throw new YangParseException(module.getName(), line,
436 "Error in augment parsing: failed to find node " + currentName + ".");
439 for (qname : path.nextLevel) {
440 currentName = qname.getLocalName();
441 if (currentParent instanceof DataNodeContainer) {
442 currentParent = (currentParent as DataNodeContainer).getDataChildByName(currentName);
443 } else if (currentParent instanceof ChoiceNode) {
444 currentParent = (currentParent as ChoiceNode).getCaseNodeByName(currentName);
446 throw new YangParseException(augment.getModuleName(), line,
447 "Error in augment parsing: failed to find node " + currentName);
450 // if node in path not found, return false
451 if (currentParent === null) {
452 throw new YangParseException(module.getName(), line,
453 "Error in augment parsing: failed to find node " + currentName + ".");
457 val oldPath = currentParent.path;
459 if (!(currentParent instanceof AugmentationTarget)) {
460 throw new YangParseException(module.getName(), line,
461 "Target of type " + currentParent.class + " cannot be augmented.");
464 switch (currentParent) {
465 case (currentParent instanceof ContainerSchemaNodeImpl): {
467 // includes container, input and output statement
468 val c = currentParent as ContainerSchemaNodeImpl;
469 val cb = c.toBuilder();
470 fillAugmentTarget(augment, cb);
471 (cb as AugmentationTargetBuilder ).addAugmentation(augment);
474 case (currentParent instanceof ListSchemaNodeImpl): {
475 val l = currentParent as ListSchemaNodeImpl;
476 val lb = l.toBuilder();
477 fillAugmentTarget(augment, lb);
478 (lb as AugmentationTargetBuilder ).addAugmentation(augment);
480 augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
481 augment.setResolved(true);
483 case (currentParent instanceof ChoiceNodeImpl): {
484 val ch = currentParent as ChoiceNodeImpl;
485 val chb = ch.toBuilder();
486 fillAugmentTarget(augment, chb);
487 (chb as AugmentationTargetBuilder ).addAugmentation(augment);
489 augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
490 augment.setResolved(true);
492 case (currentParent instanceof ChoiceCaseNodeImpl): {
493 val chc = currentParent as ChoiceCaseNodeImpl;
494 val chcb = chc.toBuilder();
495 fillAugmentTarget(augment, chcb);
496 (chcb as AugmentationTargetBuilder ).addAugmentation(augment);
498 augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
499 augment.setResolved(true);
501 case (currentParent instanceof NotificationDefinitionImpl): {
502 val nd = currentParent as NotificationDefinitionImpl;
503 val nb = nd.toBuilder();
504 fillAugmentTarget(augment, nb);
505 (nb as AugmentationTargetBuilder ).addAugmentation(augment);
507 augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
508 augment.setResolved(true);
511 augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
512 augment.setResolved(true);
516 public static def QName findFullQName(Map<String, TreeMap<Date, ModuleBuilder>> modules,
517 ModuleBuilder module, IdentityrefTypeBuilder idref) {
518 var QName result = null;
519 val String baseString = idref.getBaseString();
520 if (baseString.contains(":")) {
521 val String[] splittedBase = baseString.split(":");
522 if (splittedBase.length > 2) {
523 throw new YangParseException(module.getName(), idref.getLine(),
524 "Failed to parse identityref base: " + baseString);
526 val prefix = splittedBase.get(0);
527 val name = splittedBase.get(1);
528 val dependentModule = findDependentModuleBuilder(modules, module, prefix, idref.getLine());
529 result = new QName(dependentModule.getNamespace(), dependentModule.getRevision(), prefix, name);
531 result = new QName(module.getNamespace(), module.getRevision(), module.getPrefix(), baseString);
537 * Get module in which this node is defined.
540 * @return builder of module where this node is defined
542 public static def ModuleBuilder getParentModule(Builder node) {
543 if (node instanceof ModuleBuilder) {
544 return node as ModuleBuilder;
546 var parent = node.getParent();
547 while (!(parent instanceof ModuleBuilder)) {
548 parent = parent.getParent();
550 return parent as ModuleBuilder;