2 * Copyright (c) 2016 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
9 package org.opendaylight.mdsal.binding2.generator.impl;
11 import static com.google.common.base.Preconditions.checkArgument;
12 import static org.opendaylight.mdsal.binding2.generator.impl.AugmentToGenType.usesAugmentationToGenTypes;
13 import static org.opendaylight.mdsal.binding2.generator.util.BindingTypes.TREE_ROOT;
14 import static org.opendaylight.mdsal.binding2.generator.util.BindingTypes.augmentable;
15 import static org.opendaylight.mdsal.binding2.generator.util.Types.typeForClass;
17 import com.google.common.annotations.Beta;
18 import com.google.common.annotations.VisibleForTesting;
19 import com.google.common.base.Splitter;
20 import com.google.common.base.Strings;
21 import com.google.common.collect.Iterables;
22 import java.util.HashMap;
23 import java.util.List;
25 import java.util.regex.Pattern;
26 import org.opendaylight.mdsal.binding2.generator.impl.util.YangTextTemplate;
27 import org.opendaylight.mdsal.binding2.generator.util.Binding2GeneratorUtil;
28 import org.opendaylight.mdsal.binding2.generator.util.Binding2Mapping;
29 import org.opendaylight.mdsal.binding2.generator.util.BindingTypes;
30 import org.opendaylight.mdsal.binding2.generator.util.Types;
31 import org.opendaylight.mdsal.binding2.generator.util.generated.type.builder.GeneratedTypeBuilderImpl;
32 import org.opendaylight.mdsal.binding2.model.api.Constant;
33 import org.opendaylight.mdsal.binding2.model.api.GeneratedType;
34 import org.opendaylight.mdsal.binding2.model.api.Type;
35 import org.opendaylight.mdsal.binding2.model.api.type.builder.GeneratedTypeBuilder;
36 import org.opendaylight.mdsal.binding2.model.api.type.builder.GeneratedTypeBuilderBase;
37 import org.opendaylight.mdsal.binding2.txt.yangTemplateForModule;
38 import org.opendaylight.mdsal.binding2.txt.yangTemplateForNode;
39 import org.opendaylight.yangtools.yang.common.QName;
40 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
41 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
43 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
45 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.Module;
47 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
48 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
49 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
50 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
51 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
52 import org.opendaylight.yangtools.yang.model.api.Status;
53 import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
54 import org.opendaylight.yangtools.yang.model.api.UsesNode;
55 import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
59 * Helper util class used for generation of types in binding spec v2.
62 final class GenHelperUtil {
64 private GenHelperUtil() {
65 throw new UnsupportedOperationException("Util class");
68 private static final Pattern UNICODE_CHAR_PATTERN = Pattern.compile("\\\\+u");
69 private static final Splitter BSDOT_SPLITTER = Splitter.on("\\.");
70 private static final char NEW_LINE = '\n';
73 * Constant with the concrete name of identifier.
75 private static final String AUGMENT_IDENTIFIER_NAME = "augment-identifier";
78 * Constant with the concrete name of namespace.
80 private static final String YANG_EXT_NAMESPACE = "urn:opendaylight:yang:extension:yang-ext";
84 * Create GeneratedTypeBuilder object from module argument.
87 * Module object from which builder will be created
89 * @param verboseClassComments
91 * @return <code>GeneratedTypeBuilder</code> which is internal
92 * representation of the module
93 * @throws IllegalArgumentException
96 static GeneratedTypeBuilder moduleToDataType(final Module module, Map<Module, ModuleContext> genCtx, final boolean verboseClassComments) {
97 checkArgument(module != null, "Module reference cannot be NULL.");
99 final GeneratedTypeBuilder moduleDataTypeBuilder = moduleTypeBuilder(module, "Data", verboseClassComments);
100 addImplementedInterfaceFromUses(module, moduleDataTypeBuilder, genCtx);
101 moduleDataTypeBuilder.addImplementsType(TREE_ROOT);
102 moduleDataTypeBuilder.addComment(module.getDescription());
103 moduleDataTypeBuilder.setDescription(createDescription(module, verboseClassComments));
104 moduleDataTypeBuilder.setReference(module.getReference());
105 return moduleDataTypeBuilder;
109 * Generates type builder for <code>module</code>.
112 * Module which is source of package name for generated type
115 * string which is added to the module class name representation
117 * @param verboseClassComments
118 * @return instance of GeneratedTypeBuilder which represents
119 * <code>module</code>.
120 * @throws IllegalArgumentException
121 * if <code>module</code> is null
123 static GeneratedTypeBuilder moduleTypeBuilder(final Module module, final String postfix, final boolean verboseClassComments) {
124 checkArgument(module != null, "Module reference cannot be NULL.");
125 final String packageName = Binding2Mapping.getRootPackageName(module);
126 final String moduleName = Binding2Mapping.getClassName(module.getName()) + postfix;
128 final GeneratedTypeBuilderImpl moduleBuilder = new GeneratedTypeBuilderImpl(packageName, moduleName);
129 moduleBuilder.setDescription(createDescription(module, verboseClassComments));
130 moduleBuilder.setReference(module.getReference());
131 moduleBuilder.setModuleName(moduleName);
133 return moduleBuilder;
137 * Adds the implemented types to type builder.
139 * The method passes through the list of <i>uses</i> in
140 * {@code dataNodeContainer}. For every <i>use</i> is obtained corresponding
141 * generated type from all groupings
142 * allGroupings} which is added as <i>implements type</i> to
143 * <code>builder</code>
145 * @param dataNodeContainer
146 * element which contains the list of used YANG groupings
148 * builder to which are added implemented types according to
149 * <code>dataNodeContainer</code>
151 * @return generated type builder with all implemented types
153 private static GeneratedTypeBuilder addImplementedInterfaceFromUses(final DataNodeContainer dataNodeContainer,
154 final GeneratedTypeBuilder builder, Map<Module, ModuleContext> genCtx) {
155 for (final UsesNode usesNode : dataNodeContainer.getUses()) {
156 if (usesNode.getGroupingPath() != null) {
157 final GeneratedType genType = findGroupingByPath(usesNode.getGroupingPath(), genCtx).toInstance();
158 if (genType == null) {
159 throw new IllegalStateException("Grouping " + usesNode.getGroupingPath() + "is not resolved for "
160 + builder.getName());
163 builder.addImplementsType(genType);
169 static GeneratedTypeBuilder findGroupingByPath(final SchemaPath path, Map<Module, ModuleContext> genCtx) {
170 for (final ModuleContext ctx : genCtx.values()) {
171 final GeneratedTypeBuilder result = ctx.getGrouping(path);
172 if (result != null) {
179 private static String createDescription(final Module module, final boolean verboseClassComments) {
180 final StringBuilder sb = new StringBuilder();
181 final String moduleDescription = Binding2GeneratorUtil.encodeAngleBrackets(module.getDescription());
182 final String formattedDescription = YangTextTemplate.formatToParagraph(moduleDescription, 0);
184 if (!Strings.isNullOrEmpty(formattedDescription)) {
185 sb.append(formattedDescription);
189 if (verboseClassComments) {
191 sb.append("This class represents the following YANG schema fragment defined in module <b>");
192 sb.append(module.getName());
197 sb.append(Binding2GeneratorUtil.encodeAngleBrackets(yangTemplateForModule.render(module).body()));
201 return replaceAllIllegalChars(sb);
205 public static String replaceAllIllegalChars(final StringBuilder stringBuilder){
206 final String ret = UNICODE_CHAR_PATTERN.matcher(stringBuilder).replaceAll("\\\\\\\\u");
207 return ret.isEmpty() ? "" : ret;
211 * Adds the methods to <code>typeBuilder</code> which represent subnodes of
212 * node for which <code>typeBuilder</code> was created.
214 * The subnodes aren't mapped to the methods if they are part of grouping or
215 * augment (in this case are already part of them).
219 * @param basePackageName
220 * string contains the module package name
222 * generated type builder which represents any node. The subnodes
223 * of this node are added to the <code>typeBuilder</code> as
224 * methods. The subnode can be of type leaf, leaf-list, list,
229 * set of data schema nodes which are the children of the node
230 * for which <code>typeBuilder</code> was created
231 * @return generated type builder which is the same builder as input
232 * parameter. The getter methods (representing child nodes) could be
235 static GeneratedTypeBuilder resolveDataSchemaNodes(final Module module, final String basePackageName,
236 final GeneratedTypeBuilder parent, final GeneratedTypeBuilder childOf, final Iterable<DataSchemaNode> schemaNodes) {
237 if (schemaNodes != null && parent != null) {
238 for (final DataSchemaNode schemaNode : schemaNodes) {
239 if (!schemaNode.isAugmenting() && !schemaNode.isAddedByUses()) {
240 //TODO: design decomposition and implement it
241 //addSchemaNodeToBuilderAsMethod(basePackageName, schemaNode, parent, childOf, module);
248 static Map<Module, ModuleContext> processUsesAugments(final SchemaContext schemaContext, final
249 DataNodeContainer node, final Module module, Map<Module, ModuleContext> genCtx, Map<String,
250 Map<String, GeneratedTypeBuilder>> genTypeBuilders, final boolean verboseClassComments) {
251 final String basePackageName = Binding2Mapping.getRootPackageName(module);
252 for (final UsesNode usesNode : node.getUses()) {
253 for (final AugmentationSchema augment : usesNode.getAugmentations()) {
254 genCtx = usesAugmentationToGenTypes(schemaContext, basePackageName, augment, module, usesNode,
255 node, genCtx, genTypeBuilders, verboseClassComments);
256 genCtx = processUsesAugments(schemaContext, augment, module, genCtx, genTypeBuilders, verboseClassComments);
262 static GeneratedTypeBuilder findChildNodeByPath(final SchemaPath path, Map<Module, ModuleContext> genCtx) {
263 for (final ModuleContext ctx : genCtx.values()) {
264 final GeneratedTypeBuilder result = ctx.getChildNode(path);
265 if (result != null) {
272 static GeneratedTypeBuilder findCaseByPath(final SchemaPath path, Map<Module, ModuleContext> genCtx) {
273 for (final ModuleContext ctx : genCtx.values()) {
274 final GeneratedTypeBuilder result = ctx.getCase(path);
275 if (result != null) {
283 * Returns a generated type builder for an augmentation.
285 * The name of the type builder is equal to the name of augmented node with
286 * serial number as suffix.
290 * @param augmentPackageName
291 * string with contains the package name to which the augment
293 * @param basePackageName
294 * string with the package name to which the augmented node
296 * @param targetTypeRef
299 * augmentation schema which contains data about the child nodes
300 * and uses of augment
301 * @return generated type builder for augment in genCtx
303 static Map<Module, ModuleContext> addRawAugmentGenTypeDefinition(final Module module, final String augmentPackageName,
304 final String basePackageName, final Type targetTypeRef, final AugmentationSchema augSchema,
305 Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders, Map<Module, ModuleContext> genCtx) {
307 Map<String, GeneratedTypeBuilder> augmentBuilders = genTypeBuilders.get(augmentPackageName);
308 if (augmentBuilders == null) {
309 augmentBuilders = new HashMap<>();
310 genTypeBuilders.put(augmentPackageName, augmentBuilders);
312 final String augIdentifier = getAugmentIdentifier(augSchema.getUnknownSchemaNodes());
315 if (augIdentifier != null) {
316 augTypeName = Binding2Mapping.getClassName(augIdentifier);
318 augTypeName = augGenTypeName(augmentBuilders, targetTypeRef.getName());
321 GeneratedTypeBuilder augTypeBuilder = new GeneratedTypeBuilderImpl(augmentPackageName, augTypeName);
323 augTypeBuilder.addImplementsType(BindingTypes.TREE_NODE);
324 augTypeBuilder.addImplementsType(Types.augmentationTypeFor(targetTypeRef));
325 annotateDeprecatedIfNecessary(augSchema.getStatus(), augTypeBuilder);
326 augTypeBuilder = addImplementedInterfaceFromUses(augSchema, augTypeBuilder, genCtx);
328 augTypeBuilder = augSchemaNodeToMethods(module, basePackageName, augTypeBuilder, augTypeBuilder, augSchema
330 augmentBuilders.put(augTypeName, augTypeBuilder);
332 if(!augSchema.getChildNodes().isEmpty()) {
333 genCtx.get(module).addTypeToAugmentation(augTypeBuilder, augSchema);
336 genCtx.get(module).addAugmentType(augTypeBuilder);
341 * Adds the methods to <code>typeBuilder</code> what represents subnodes of
342 * node for which <code>typeBuilder</code> was created.
346 * @param basePackageName
347 * string contains the module package name
349 * generated type builder which represents any node. The subnodes
350 * of this node are added to the <code>typeBuilder</code> as
351 * methods. The subnode can be of type leaf, leaf-list, list,
356 * set of data schema nodes which are the children of the node
357 * for which <code>typeBuilder</code> was created
358 * @return generated type builder which is the same object as the input
359 * parameter <code>typeBuilder</code>. The getter method could be
362 private static GeneratedTypeBuilder augSchemaNodeToMethods(final Module module, final String basePackageName,
363 final GeneratedTypeBuilder typeBuilder, final GeneratedTypeBuilder childOf,
364 final Iterable<DataSchemaNode> schemaNodes) {
365 if ((schemaNodes != null) && (typeBuilder != null)) {
366 for (final DataSchemaNode schemaNode : schemaNodes) {
367 if (!schemaNode.isAugmenting()) {
368 //TODO: design decomposition and implement it
369 //addSchemaNodeToBuilderAsMethod(basePackageName, schemaNode, typeBuilder, childOf, module);
377 * @param unknownSchemaNodes
378 * @return nodeParameter of UnknownSchemaNode
380 private static String getAugmentIdentifier(final List<UnknownSchemaNode> unknownSchemaNodes) {
381 for (final UnknownSchemaNode unknownSchemaNode : unknownSchemaNodes) {
382 final QName nodeType = unknownSchemaNode.getNodeType();
383 if (AUGMENT_IDENTIFIER_NAME.equals(nodeType.getLocalName())
384 && YANG_EXT_NAMESPACE.equals(nodeType.getNamespace().toString())) {
385 return unknownSchemaNode.getNodeParameter();
392 * Returns first unique name for the augment generated type builder. The
393 * generated type builder name for augment consists from name of augmented
394 * node and serial number of its augmentation.
397 * map of builders which were created in the package to which the
398 * augmentation belongs
400 * string with name of augmented node
401 * @return string with unique name for augmentation builder
403 private static String augGenTypeName(final Map<String, GeneratedTypeBuilder> builders, final String genTypeName) {
405 if (builders != null) {
406 while (builders.containsKey(genTypeName + index)) {
410 return genTypeName + index;
413 static GeneratedTypeBuilder addDefaultInterfaceDefinition(final String packageName, final SchemaNode
414 schemaNode, final Module module, Map<Module, ModuleContext> genCtx, final SchemaContext schemaContext,
415 final boolean verboseClassComments, Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders) {
416 return addDefaultInterfaceDefinition(packageName, schemaNode, null, module, genCtx, schemaContext,
417 verboseClassComments, genTypeBuilders);
421 * Instantiates generated type builder with <code>packageName</code> and
422 * <code>schemaNode</code>.
424 * The new builder always implements
425 * {@link org.opendaylight.mdsal.binding2.spec.TreeNode TreeNode}.<br>
426 * If <code>schemaNode</code> is instance of GroupingDefinition it also
427 * implements {@link org.opendaylight.mdsal.binding2.spec.Augmentable
429 * If <code>schemaNode</code> is instance of
430 * {@link org.opendaylight.yangtools.yang.model.api.DataNodeContainer
431 * DataNodeContainer} it can also implement nodes which are specified in
435 * string with the name of the package to which
436 * <code>schemaNode</code> belongs.
438 * schema node for which is created generated type builder
440 * parent type (can be null)
441 * @param schemaContext
442 * @return generated type builder <code>schemaNode</code>
444 private static GeneratedTypeBuilder addDefaultInterfaceDefinition(final String packageName, final SchemaNode
445 schemaNode, final Type parent, final Module module, Map<Module, ModuleContext> genCtx,
446 final SchemaContext schemaContext, final boolean verboseClassComments, Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders) {
447 GeneratedTypeBuilder it = addRawInterfaceDefinition(packageName, schemaNode, schemaContext, "",
448 verboseClassComments, genTypeBuilders);
449 if (parent == null) {
450 it.addImplementsType(BindingTypes.TREE_NODE);
452 it.addImplementsType(BindingTypes.treeChildNode(parent));
454 if (!(schemaNode instanceof GroupingDefinition)) {
455 it.addImplementsType(augmentable(it));
458 if (schemaNode instanceof DataNodeContainer) {
459 //TODO: design decomposition and implement it
460 //groupingsToGenTypes(module, ((DataNodeContainer) schemaNode).getGroupings());
461 it = addImplementedInterfaceFromUses((DataNodeContainer) schemaNode, it, genCtx);
468 * Returns reference to generated type builder for specified
469 * <code>schemaNode</code> with <code>packageName</code>.
471 * Firstly the generated type builder is searched in
472 * {@link BindingGeneratorImpl#genTypeBuilders genTypeBuilders}. If it isn't
473 * found it is created and added to <code>genTypeBuilders</code>.
476 * string with the package name to which returning generated type
479 * schema node which provide data about the schema node name
480 * @param schemaContext
482 * return type name prefix
483 * @return generated type builder for <code>schemaNode</code>
484 * @throws IllegalArgumentException
486 * <li>if <code>schemaNode</code> is null</li>
487 * <li>if <code>packageName</code> is null</li>
488 * <li>if QName of schema node is null</li>
489 * <li>if schemaNode name is null</li>
493 private static GeneratedTypeBuilder addRawInterfaceDefinition(final String packageName, final SchemaNode schemaNode,
494 final SchemaContext schemaContext, final String prefix, final boolean verboseClassComments,
495 Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders) {
496 checkArgument(schemaNode != null, "Data Schema Node cannot be NULL.");
497 checkArgument(packageName != null, "Package Name for Generated Type cannot be NULL.");
498 checkArgument(schemaNode.getQName() != null, "QName for Data Schema Node cannot be NULL.");
499 final String schemaNodeName = schemaNode.getQName().getLocalName();
500 checkArgument(schemaNodeName != null, "Local Name of QName for Data Schema Node cannot be NULL.");
503 if (prefix == null) {
504 genTypeName = Binding2Mapping.getClassName(schemaNodeName);
506 genTypeName = prefix + Binding2Mapping.getClassName(schemaNodeName);
509 final GeneratedTypeBuilderImpl newType = new GeneratedTypeBuilderImpl(packageName, genTypeName);
510 final Module module = SchemaContextUtil.findParentModule(schemaContext, schemaNode);
511 qNameConstant(newType, Binding2Mapping.QNAME_STATIC_FIELD_NAME, schemaNode.getQName());
512 newType.addComment(schemaNode.getDescription());
513 newType.setDescription(createDescription(schemaNode, newType.getFullyQualifiedName(), schemaContext, verboseClassComments));
514 newType.setReference(schemaNode.getReference());
515 newType.setSchemaPath((List<QName>) schemaNode.getPath().getPathFromRoot());
516 newType.setModuleName(module.getName());
518 //FIXME: update genTypeBuilders for callers
519 if (!genTypeBuilders.containsKey(packageName)) {
520 final Map<String, GeneratedTypeBuilder> builders = new HashMap<>();
521 builders.put(genTypeName, newType);
522 genTypeBuilders.put(packageName, builders);
524 final Map<String, GeneratedTypeBuilder> builders = genTypeBuilders.get(packageName);
525 if (!builders.containsKey(genTypeName)) {
526 builders.put(genTypeName, newType);
533 private static Constant qNameConstant(final GeneratedTypeBuilderBase<?> toBuilder, final String constantName,
535 return toBuilder.addConstant(typeForClass(QName.class), constantName, name);
538 private static String createDescription(final SchemaNode schemaNode, final String fullyQualifiedName,
539 final SchemaContext schemaContext, final boolean verboseClassComments) {
540 final StringBuilder sb = new StringBuilder();
541 final String nodeDescription = Binding2GeneratorUtil.encodeAngleBrackets(schemaNode.getDescription());
542 final String formattedDescription = YangTextTemplate.formatToParagraph(nodeDescription, 0);
544 if (!Strings.isNullOrEmpty(formattedDescription)) {
545 sb.append(formattedDescription);
549 if (verboseClassComments) {
550 final Module module = SchemaContextUtil.findParentModule(schemaContext, schemaNode);
551 final StringBuilder linkToBuilderClass = new StringBuilder();
552 final String[] namespace = Iterables.toArray(BSDOT_SPLITTER.split(fullyQualifiedName), String.class);
553 final String className = namespace[namespace.length - 1];
555 if (hasBuilderClass(schemaNode)) {
556 linkToBuilderClass.append(className);
557 linkToBuilderClass.append("Builder");
561 sb.append("This class represents the following YANG schema fragment defined in module <b>");
562 sb.append(module.getName());
567 sb.append(Binding2GeneratorUtil.encodeAngleBrackets(yangTemplateForNode.render(schemaNode).body()));
570 sb.append("The schema path to identify an instance is");
573 sb.append(YangTextTemplate.formatSchemaPath(module.getName(), schemaNode.getPath().getPathFromRoot()));
577 if (hasBuilderClass(schemaNode)) {
579 sb.append("<p>To create instances of this class use " + "{@link " + linkToBuilderClass + "}.");
582 sb.append(linkToBuilderClass);
584 if (schemaNode instanceof ListSchemaNode) {
585 final List<QName> keyDef = ((ListSchemaNode)schemaNode).getKeyDefinition();
586 if (keyDef != null && !keyDef.isEmpty()) {
588 sb.append(className);
596 return replaceAllIllegalChars(sb);
599 private static void annotateDeprecatedIfNecessary(final Status status, final GeneratedTypeBuilder builder) {
600 if (status == Status.DEPRECATED) {
601 builder.addAnnotation("", "Deprecated");
605 private static boolean hasBuilderClass(final SchemaNode schemaNode) {
606 if (schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode ||
607 schemaNode instanceof RpcDefinition || schemaNode instanceof NotificationDefinition) {