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.Module;
22 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
23 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
24 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
25 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
26 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
27 import org.opendaylight.yangtools.yang.parser.builder.api.AugmentationSchemaBuilder;
28 import org.opendaylight.yangtools.yang.parser.builder.api.AugmentationTargetBuilder;
29 import org.opendaylight.yangtools.yang.parser.builder.api.Builder;
30 import org.opendaylight.yangtools.yang.parser.builder.api.DataNodeContainerBuilder;
31 import org.opendaylight.yangtools.yang.parser.builder.api.DataSchemaNodeBuilder;
32 import org.opendaylight.yangtools.yang.parser.builder.api.GroupingBuilder;
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;
40 import org.opendaylight.yangtools.yang.parser.builder.impl.ContainerSchemaNodeBuilder.ContainerSchemaNodeImpl;
41 import org.opendaylight.yangtools.yang.parser.builder.impl.IdentityrefTypeBuilder;
42 import org.opendaylight.yangtools.yang.parser.builder.impl.ListSchemaNodeBuilder;
43 import org.opendaylight.yangtools.yang.parser.builder.impl.ListSchemaNodeBuilder.ListSchemaNodeImpl;
44 import org.opendaylight.yangtools.yang.parser.builder.impl.ModuleBuilder;
45 import org.opendaylight.yangtools.yang.parser.builder.impl.NotificationBuilder;
46 import org.opendaylight.yangtools.yang.parser.builder.impl.NotificationBuilder.NotificationDefinitionImpl;
48 public final class ParserUtils {
50 private ParserUtils() {
54 * Create new SchemaPath from given path and qname.
60 public static SchemaPath createSchemaPath(SchemaPath schemaPath, QName... qname) {
61 List<QName> path = new ArrayList<>(schemaPath.getPath());
62 path.addAll(Arrays.asList(qname));
63 return new SchemaPath(path, schemaPath.isAbsolute());
67 * Get module import referenced by given prefix.
72 * prefix associated with import
73 * @return ModuleImport based on given prefix
75 public static ModuleImport getModuleImport(final ModuleBuilder builder, final String prefix) {
76 ModuleImport moduleImport = null;
77 for (ModuleImport mi : builder.getModuleImports()) {
78 if (mi.getPrefix().equals(prefix)) {
87 * Find dependent module based on given prefix
90 * all available modules
94 * target module prefix
96 * current line in yang model
97 * @return module builder if found, null otherwise
99 public static ModuleBuilder findDependentModuleBuilder(final Map<String, TreeMap<Date, ModuleBuilder>> modules,
100 final ModuleBuilder module, final String prefix, final int line) {
101 ModuleBuilder dependentModule = null;
102 Date dependentModuleRevision = null;
104 if (prefix.equals(module.getPrefix())) {
105 dependentModule = module;
107 final ModuleImport dependentModuleImport = getModuleImport(module, prefix);
108 if (dependentModuleImport == null) {
109 throw new YangParseException(module.getName(), line, "No import found with prefix '" + prefix + "'.");
111 final String dependentModuleName = dependentModuleImport.getModuleName();
112 dependentModuleRevision = dependentModuleImport.getRevision();
114 final TreeMap<Date, ModuleBuilder> moduleBuildersByRevision = modules.get(dependentModuleName);
115 if (moduleBuildersByRevision == null) {
118 if (dependentModuleRevision == null) {
119 dependentModule = moduleBuildersByRevision.lastEntry().getValue();
121 dependentModule = moduleBuildersByRevision.get(dependentModuleRevision);
124 return dependentModule;
128 * Find module from context based on prefix.
132 * @param currentModule
135 * current prefix used to reference dependent module
137 * current line in yang model
138 * @return module based on given prefix if found in context, null otherwise
140 public static Module findModuleFromContext(final SchemaContext context, final ModuleBuilder currentModule,
141 final String prefix, final int line) {
142 TreeMap<Date, Module> modulesByRevision = new TreeMap<Date, Module>();
144 final ModuleImport dependentModuleImport = ParserUtils.getModuleImport(currentModule, prefix);
145 if (dependentModuleImport == null) {
146 throw new YangParseException(currentModule.getName(), line, "No import found with prefix '" + prefix + "'.");
148 final String dependentModuleName = dependentModuleImport.getModuleName();
149 final Date dependentModuleRevision = dependentModuleImport.getRevision();
151 for (Module contextModule : context.getModules()) {
152 if (contextModule.getName().equals(dependentModuleName)) {
153 Date revision = contextModule.getRevision();
154 if (revision == null) {
155 revision = new Date(0L);
157 modulesByRevision.put(revision, contextModule);
162 Module result = null;
163 if (dependentModuleRevision == null) {
164 result = modulesByRevision.get(modulesByRevision.firstKey());
166 result = modulesByRevision.get(dependentModuleRevision);
173 * Parse XPath string.
177 * @return SchemaPath from given String
179 public static SchemaPath parseXPathString(final String xpathString) {
180 final boolean absolute = xpathString.startsWith("/");
181 final String[] splittedPath = xpathString.split("/");
182 final List<QName> path = new ArrayList<QName>();
184 for (String pathElement : splittedPath) {
185 if (pathElement.length() > 0) {
186 final String[] splittedElement = pathElement.split(":");
187 if (splittedElement.length == 1) {
188 name = new QName(null, null, null, splittedElement[0]);
190 name = new QName(null, null, splittedElement[0], splittedElement[1]);
195 return new SchemaPath(path, absolute);
199 * Add all augment's child nodes to given target.
202 * builder of augment statement
204 * augmentation target node
206 public static void fillAugmentTarget(final AugmentationSchemaBuilder augment, final DataNodeContainerBuilder target) {
207 for (DataSchemaNodeBuilder child : augment.getChildNodeBuilders()) {
208 DataSchemaNodeBuilder childCopy = CopyUtils.copy(child, target, false);
209 childCopy.setAugmenting(true);
210 correctNodePath(child, target.getPath());
211 correctNodePath(childCopy, target.getPath());
213 target.addChildNode(childCopy);
214 } catch (YangParseException e) {
215 // more descriptive message
216 throw new YangParseException(augment.getModuleName(), augment.getLine(),
217 "Failed to perform augmentation: " + e.getMessage());
221 for (UsesNodeBuilder usesNode : augment.getUsesNodes()) {
222 UsesNodeBuilder copy = CopyUtils.copyUses(usesNode, target);
223 target.addUsesNode(copy);
228 * Add all augment's child nodes to given target.
231 * builder of augment statement
233 * augmentation target choice node
235 public static void fillAugmentTarget(final AugmentationSchemaBuilder augment, final ChoiceBuilder target) {
236 for (DataSchemaNodeBuilder builder : augment.getChildNodeBuilders()) {
237 DataSchemaNodeBuilder childCopy = CopyUtils.copy(builder, target, false);
238 childCopy.setAugmenting(true);
239 correctNodePath(builder, target.getPath());
240 correctNodePath(childCopy, target.getPath());
241 target.addCase(childCopy);
243 for (UsesNodeBuilder usesNode : augment.getUsesNodes()) {
244 if (usesNode != null) {
245 throw new YangParseException(augment.getModuleName(), augment.getLine(),
246 "Error in augment parsing: cannot augment uses to choice");
252 * Create new schema path of node based on parent node schema path.
256 * @param parentSchemaPath
257 * schema path of node parent
259 static void correctNodePath(final SchemaNodeBuilder node, final SchemaPath parentSchemaPath) {
261 List<QName> targetNodePath = new ArrayList<QName>(parentSchemaPath.getPath());
262 targetNodePath.add(node.getQName());
263 node.setPath(new SchemaPath(targetNodePath, true));
265 // set correct path for all child nodes
266 if (node instanceof DataNodeContainerBuilder) {
267 DataNodeContainerBuilder dataNodeContainer = (DataNodeContainerBuilder) node;
268 for (DataSchemaNodeBuilder child : dataNodeContainer.getChildNodeBuilders()) {
269 correctNodePath(child, node.getPath());
273 // set correct path for all cases
274 if (node instanceof ChoiceBuilder) {
275 ChoiceBuilder choiceBuilder = (ChoiceBuilder) node;
276 for (ChoiceCaseBuilder choiceCaseBuilder : choiceBuilder.getCases()) {
277 correctNodePath(choiceCaseBuilder, node.getPath());
283 * Find augment target node and perform augmentation.
286 * @param firstNodeParent
287 * parent of first node in path
289 * path to augment target
290 * @return true if augmentation process succeed, false otherwise
292 public static boolean processAugmentation(final AugmentationSchemaBuilder augment, final Builder firstNodeParent,
293 final List<QName> path) {
294 // traverse augment target path and try to reach target node
295 String currentName = null;
296 Builder currentParent = firstNodeParent;
298 for (int i = 0; i < path.size(); i++) {
299 QName qname = path.get(i);
301 currentName = qname.getLocalName();
302 if (currentParent instanceof DataNodeContainerBuilder) {
303 DataSchemaNodeBuilder nodeFound = ((DataNodeContainerBuilder) currentParent)
304 .getDataChildByName(currentName);
305 // if not found as regular child, search in uses
306 if (nodeFound == null) {
307 boolean found = false;
308 for (UsesNodeBuilder unb : ((DataNodeContainerBuilder) currentParent).getUsesNodes()) {
309 DataSchemaNodeBuilder result = findNodeInUses(currentName, unb);
310 if (result != null) {
311 currentParent = result;
316 // if not found even in uses nodes, return false
321 currentParent = nodeFound;
323 } else if (currentParent instanceof ChoiceBuilder) {
324 currentParent = ((ChoiceBuilder) currentParent).getCaseNodeByName(currentName);
326 throw new YangParseException(augment.getModuleName(), augment.getLine(),
327 "Error in augment parsing: failed to find node " + currentName);
330 // if node in path not found, return false
331 if (currentParent == null) {
335 if (!(currentParent instanceof DataSchemaNodeBuilder)) {
336 throw new YangParseException(
337 augment.getModuleName(),
339 "Error in augment parsing: The target node MUST be either a container, list, choice, case, input, output, or notification node.");
342 if (currentParent instanceof ChoiceBuilder) {
343 fillAugmentTarget(augment, (ChoiceBuilder) currentParent);
345 fillAugmentTarget(augment, (DataNodeContainerBuilder) currentParent);
347 ((AugmentationTargetBuilder) currentParent).addAugmentation(augment);
348 SchemaPath oldPath = ((DataSchemaNodeBuilder) currentParent).getPath();
349 augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
350 augment.setResolved(true);
356 * Find node with given name in uses target.
359 * name of node to find
361 * uses node which target grouping should be searched
362 * @return node with given name if found, null otherwise
364 private static DataSchemaNodeBuilder findNodeInUses(String localName, UsesNodeBuilder uses) {
365 GroupingBuilder target = uses.getGroupingBuilder();
366 for (DataSchemaNodeBuilder child : target.getChildNodeBuilders()) {
367 if (child.getQName().getLocalName().equals(localName)) {
371 for (UsesNodeBuilder usesNode : target.getUsesNodes()) {
372 DataSchemaNodeBuilder result = findNodeInUses(localName, usesNode);
373 if (result != null) {
381 * Find augment target node in given context and perform augmentation.
385 * path to augment target
389 * current prefix of target module
391 * SchemaContext containing already resolved modules
392 * @return true if augment process succeed, false otherwise
394 public static boolean processAugmentationOnContext(final AugmentationSchemaBuilder augment, final List<QName> path,
395 final ModuleBuilder module, final String prefix, final SchemaContext context) {
396 final int line = augment.getLine();
397 final Module dependentModule = findModuleFromContext(context, module, prefix, line);
398 if (dependentModule == null) {
399 throw new YangParseException(module.getName(), line,
400 "Error in augment parsing: failed to find module with prefix " + prefix + ".");
403 String currentName = path.get(0).getLocalName();
404 SchemaNode currentParent = dependentModule.getDataChildByName(currentName);
405 if (currentParent == null) {
406 Set<NotificationDefinition> notifications = dependentModule.getNotifications();
407 for (NotificationDefinition ntf : notifications) {
408 if (ntf.getQName().getLocalName().equals(currentName)) {
414 if (currentParent == null) {
415 throw new YangParseException(module.getName(), line, "Error in augment parsing: failed to find node "
416 + currentName + ".");
419 for (int i = 1; i < path.size(); i++) {
420 currentName = path.get(i).getLocalName();
421 if (currentParent instanceof DataNodeContainer) {
422 currentParent = ((DataNodeContainer) currentParent).getDataChildByName(currentName);
423 } else if (currentParent instanceof ChoiceNode) {
424 currentParent = ((ChoiceNode) currentParent).getCaseNodeByName(currentName);
426 throw new YangParseException(augment.getModuleName(), line,
427 "Error in augment parsing: failed to find node " + currentName);
429 // if node in path not found, return false
430 if (currentParent == null) {
431 throw new YangParseException(module.getName(), line, "Error in augment parsing: failed to find node "
432 + currentName + ".");
436 if (currentParent instanceof ContainerSchemaNodeImpl) {
437 // includes container, input and output statement
438 ContainerSchemaNodeImpl c = (ContainerSchemaNodeImpl) currentParent;
439 ContainerSchemaNodeBuilder cb = c.toBuilder();
440 fillAugmentTarget(augment, cb);
441 ((AugmentationTargetBuilder) cb).addAugmentation(augment);
442 SchemaPath oldPath = cb.getPath();
444 augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
445 augment.setResolved(true);
446 } else if (currentParent instanceof ListSchemaNodeImpl) {
447 ListSchemaNodeImpl l = (ListSchemaNodeImpl) currentParent;
448 ListSchemaNodeBuilder lb = l.toBuilder();
449 fillAugmentTarget(augment, lb);
450 ((AugmentationTargetBuilder) lb).addAugmentation(augment);
451 SchemaPath oldPath = lb.getPath();
453 augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
454 augment.setResolved(true);
455 } else if (currentParent instanceof ChoiceNodeImpl) {
456 ChoiceNodeImpl ch = (ChoiceNodeImpl) currentParent;
457 ChoiceBuilder chb = ch.toBuilder();
458 fillAugmentTarget(augment, chb);
459 ((AugmentationTargetBuilder) chb).addAugmentation(augment);
460 SchemaPath oldPath = chb.getPath();
462 augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
463 augment.setResolved(true);
464 } else if (currentParent instanceof ChoiceCaseNodeImpl) {
465 ChoiceCaseNodeImpl chc = (ChoiceCaseNodeImpl) currentParent;
466 ChoiceCaseBuilder chcb = chc.toBuilder();
467 fillAugmentTarget(augment, chcb);
468 ((AugmentationTargetBuilder) chcb).addAugmentation(augment);
469 SchemaPath oldPath = chcb.getPath();
471 augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
472 augment.setResolved(true);
473 } else if (currentParent instanceof NotificationDefinitionImpl) {
474 NotificationDefinitionImpl nd = (NotificationDefinitionImpl) currentParent;
475 NotificationBuilder nb = nd.toBuilder();
476 fillAugmentTarget(augment, nb);
477 ((AugmentationTargetBuilder) nb).addAugmentation(augment);
478 SchemaPath oldPath = nb.getPath();
480 augment.setTargetPath(new SchemaPath(oldPath.getPath(), oldPath.isAbsolute()));
481 augment.setResolved(true);
483 throw new YangParseException(module.getName(), line, "Target of type " + currentParent.getClass()
484 + " cannot be augmented.");
490 public static QName findFullQName(final Map<String, TreeMap<Date, ModuleBuilder>> modules,
491 final ModuleBuilder module, final IdentityrefTypeBuilder idref) {
493 String baseString = idref.getBaseString();
494 if (baseString.contains(":")) {
495 String[] splittedBase = baseString.split(":");
496 if (splittedBase.length > 2) {
497 throw new YangParseException(module.getName(), idref.getLine(), "Failed to parse identityref base: "
500 String prefix = splittedBase[0];
501 String name = splittedBase[1];
502 ModuleBuilder dependentModule = findDependentModuleBuilder(modules, module, prefix, idref.getLine());
503 result = new QName(dependentModule.getNamespace(), dependentModule.getRevision(), prefix, name);
505 result = new QName(module.getNamespace(), module.getRevision(), module.getPrefix(), baseString);
511 * Get module in which this node is defined.
514 * @return builder of module where this node is defined
516 public static ModuleBuilder getParentModule(Builder node) {
517 if (node instanceof ModuleBuilder) {
518 return (ModuleBuilder) node;
521 Builder parent = node.getParent();
522 while (!(parent instanceof ModuleBuilder)) {
523 parent = parent.getParent();
525 return (ModuleBuilder) parent;