2 * Copyright (c) 2017 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.binding.javav2.generator.impl;
11 import com.google.common.annotations.Beta;
12 import com.google.common.annotations.VisibleForTesting;
13 import com.google.common.base.Preconditions;
14 import com.google.common.base.Splitter;
15 import com.google.common.base.Strings;
16 import com.google.common.collect.Iterables;
17 import java.util.HashMap;
18 import java.util.List;
20 import java.util.regex.Pattern;
21 import org.opendaylight.mdsal.binding.javav2.generator.impl.txt.yangTemplateForModule;
22 import org.opendaylight.mdsal.binding.javav2.generator.impl.txt.yangTemplateForNode;
23 import org.opendaylight.mdsal.binding.javav2.generator.impl.util.YangTextTemplate;
24 import org.opendaylight.mdsal.binding.javav2.generator.util.BindingGeneratorUtil;
25 import org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes;
26 import org.opendaylight.mdsal.binding.javav2.generator.util.Types;
27 import org.opendaylight.mdsal.binding.javav2.generator.util.generated.type.builder.GeneratedTypeBuilderImpl;
28 import org.opendaylight.mdsal.binding.javav2.model.api.Constant;
29 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedType;
30 import org.opendaylight.mdsal.binding.javav2.model.api.Type;
31 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedTypeBuilder;
32 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedTypeBuilderBase;
33 import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
34 import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentable;
35 import org.opendaylight.mdsal.binding.javav2.util.BindingMapping;
36 import org.opendaylight.yangtools.yang.common.QName;
37 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
38 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
40 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
42 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.Module;
44 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
45 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
46 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
47 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
49 import org.opendaylight.yangtools.yang.model.api.Status;
50 import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
51 import org.opendaylight.yangtools.yang.model.api.UsesNode;
52 import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
55 * Helper util class used for generation of types in binding spec v2.
58 final class GenHelperUtil {
60 private static final Pattern UNICODE_CHAR_PATTERN = Pattern.compile("\\\\+u");
61 private static final Splitter BSDOT_SPLITTER = Splitter.on("\\.");
62 private static final char NEW_LINE = '\n';
65 * Constant with the concrete name of identifier.
67 private static final String AUGMENT_IDENTIFIER_NAME = "augment-identifier";
70 * Constant with the concrete name of namespace.
72 private static final String YANG_EXT_NAMESPACE = "urn:opendaylight:yang:extension:yang-ext";
74 private GenHelperUtil() {
75 throw new UnsupportedOperationException("Util class");
79 * Create GeneratedTypeBuilder object from module argument.
82 * Module object from which builder will be created
84 * @param verboseClassComments
86 * @return <code>GeneratedTypeBuilder</code> which is internal
87 * representation of the module
88 * @throws IllegalArgumentException
91 static GeneratedTypeBuilder moduleToDataType(final Module module, Map<Module, ModuleContext> genCtx, final boolean verboseClassComments) {
92 Preconditions.checkArgument(module != null, "Module reference cannot be NULL.");
94 final GeneratedTypeBuilder moduleDataTypeBuilder = moduleTypeBuilder(module, "Data", verboseClassComments);
95 addImplementedInterfaceFromUses(module, moduleDataTypeBuilder, genCtx);
96 moduleDataTypeBuilder.addImplementsType(BindingTypes.TREE_ROOT);
97 moduleDataTypeBuilder.addComment(module.getDescription());
98 moduleDataTypeBuilder.setDescription(createDescription(module, verboseClassComments));
99 moduleDataTypeBuilder.setReference(module.getReference());
100 return moduleDataTypeBuilder;
104 * Generates type builder for <code>module</code>.
107 * Module which is source of package name for generated type
110 * string which is added to the module class name representation
112 * @param verboseClassComments
113 * @return instance of GeneratedTypeBuilder which represents
114 * <code>module</code>.
115 * @throws IllegalArgumentException
116 * if <code>module</code> is null
118 static GeneratedTypeBuilder moduleTypeBuilder(final Module module, final String postfix, final boolean verboseClassComments) {
119 Preconditions.checkArgument(module != null, "Module reference cannot be NULL.");
120 final String packageName = BindingMapping.getRootPackageName(module);
121 final String moduleName = BindingMapping.getClassName(module.getName()) + postfix;
123 final GeneratedTypeBuilderImpl moduleBuilder = new GeneratedTypeBuilderImpl(packageName, moduleName);
124 moduleBuilder.setDescription(createDescription(module, verboseClassComments));
125 moduleBuilder.setReference(module.getReference());
126 moduleBuilder.setModuleName(moduleName);
128 return moduleBuilder;
132 * Adds the implemented types to type builder.
134 * The method passes through the list of <i>uses</i> in
135 * {@code dataNodeContainer}. For every <i>use</i> is obtained corresponding
136 * generated type from all groupings
137 * allGroupings} which is added as <i>implements type</i> to
138 * <code>builder</code>
140 * @param dataNodeContainer
141 * element which contains the list of used YANG groupings
143 * builder to which are added implemented types according to
144 * <code>dataNodeContainer</code>
146 * @return generated type builder with all implemented types
148 private static GeneratedTypeBuilder addImplementedInterfaceFromUses(final DataNodeContainer dataNodeContainer,
149 final GeneratedTypeBuilder builder, Map<Module, ModuleContext> genCtx) {
150 for (final UsesNode usesNode : dataNodeContainer.getUses()) {
151 if (usesNode.getGroupingPath() != null) {
152 final GeneratedType genType = findGroupingByPath(usesNode.getGroupingPath(), genCtx).toInstance();
153 if (genType == null) {
154 throw new IllegalStateException("Grouping " + usesNode.getGroupingPath() + "is not resolved for "
155 + builder.getName());
158 builder.addImplementsType(genType);
164 static GeneratedTypeBuilder findGroupingByPath(final SchemaPath path, Map<Module, ModuleContext> genCtx) {
165 for (final ModuleContext ctx : genCtx.values()) {
166 final GeneratedTypeBuilder result = ctx.getGrouping(path);
167 if (result != null) {
174 private static String createDescription(final Module module, final boolean verboseClassComments) {
175 final StringBuilder sb = new StringBuilder();
176 final String moduleDescription = BindingGeneratorUtil.encodeAngleBrackets(module.getDescription());
177 final String formattedDescription = YangTextTemplate.formatToParagraph(moduleDescription, 0);
179 if (!Strings.isNullOrEmpty(formattedDescription)) {
180 sb.append(formattedDescription);
184 if (verboseClassComments) {
186 sb.append("This class represents the following YANG schema fragment defined in module <b>");
187 sb.append(module.getName());
192 sb.append(BindingGeneratorUtil.encodeAngleBrackets(yangTemplateForModule.render(module).body()));
196 return replaceAllIllegalChars(sb);
200 public static String replaceAllIllegalChars(final StringBuilder stringBuilder){
201 final String ret = UNICODE_CHAR_PATTERN.matcher(stringBuilder).replaceAll("\\\\\\\\u");
202 return ret.isEmpty() ? "" : ret;
206 * Adds the methods to <code>typeBuilder</code> which represent subnodes of
207 * node for which <code>typeBuilder</code> was created.
209 * The subnodes aren't mapped to the methods if they are part of grouping or
210 * augment (in this case are already part of them).
214 * @param basePackageName
215 * string contains the module package name
217 * generated type builder which represents any node. The subnodes
218 * of this node are added to the <code>typeBuilder</code> as
219 * methods. The subnode can be of type leaf, leaf-list, list,
224 * set of data schema nodes which are the children of the node
225 * for which <code>typeBuilder</code> was created
226 * @return generated type builder which is the same builder as input
227 * parameter. The getter methods (representing child nodes) could be
230 static GeneratedTypeBuilder resolveDataSchemaNodes(final Module module, final String basePackageName,
231 final GeneratedTypeBuilder parent, final GeneratedTypeBuilder childOf, final Iterable<DataSchemaNode> schemaNodes) {
232 if (schemaNodes != null && parent != null) {
233 for (final DataSchemaNode schemaNode : schemaNodes) {
234 if (!schemaNode.isAugmenting() && !schemaNode.isAddedByUses()) {
235 //TODO: design decomposition and implement it
236 //addSchemaNodeToBuilderAsMethod(basePackageName, schemaNode, parent, childOf, module);
243 static Map<Module, ModuleContext> processUsesAugments(final SchemaContext schemaContext, final
244 DataNodeContainer node, final Module module, Map<Module, ModuleContext> genCtx, Map<String,
245 Map<String, GeneratedTypeBuilder>> genTypeBuilders, final boolean verboseClassComments) {
246 final String basePackageName = BindingMapping.getRootPackageName(module);
247 for (final UsesNode usesNode : node.getUses()) {
248 for (final AugmentationSchema augment : usesNode.getAugmentations()) {
249 genCtx = AugmentToGenType.usesAugmentationToGenTypes(schemaContext, basePackageName, augment, module,
251 node, genCtx, genTypeBuilders, verboseClassComments);
252 genCtx = processUsesAugments(schemaContext, augment, module, genCtx, genTypeBuilders, verboseClassComments);
258 static GeneratedTypeBuilder findChildNodeByPath(final SchemaPath path, Map<Module, ModuleContext> genCtx) {
259 for (final ModuleContext ctx : genCtx.values()) {
260 final GeneratedTypeBuilder result = ctx.getChildNode(path);
261 if (result != null) {
268 static GeneratedTypeBuilder findCaseByPath(final SchemaPath path, Map<Module, ModuleContext> genCtx) {
269 for (final ModuleContext ctx : genCtx.values()) {
270 final GeneratedTypeBuilder result = ctx.getCase(path);
271 if (result != null) {
279 * Returns a generated type builder for an augmentation.
281 * The name of the type builder is equal to the name of augmented node with
282 * serial number as suffix.
286 * @param augmentPackageName
287 * string with contains the package name to which the augment
289 * @param basePackageName
290 * string with the package name to which the augmented node
292 * @param targetTypeRef
295 * augmentation schema which contains data about the child nodes
296 * and uses of augment
297 * @return generated type builder for augment in genCtx
299 static Map<Module, ModuleContext> addRawAugmentGenTypeDefinition(final Module module, final String augmentPackageName,
300 final String basePackageName, final Type targetTypeRef, final AugmentationSchema augSchema,
301 Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders, Map<Module, ModuleContext> genCtx) {
303 Map<String, GeneratedTypeBuilder> augmentBuilders = genTypeBuilders.get(augmentPackageName);
304 if (augmentBuilders == null) {
305 augmentBuilders = new HashMap<>();
306 genTypeBuilders.put(augmentPackageName, augmentBuilders);
308 final String augIdentifier = getAugmentIdentifier(augSchema.getUnknownSchemaNodes());
311 if (augIdentifier != null) {
312 augTypeName = BindingMapping.getClassName(augIdentifier);
314 augTypeName = augGenTypeName(augmentBuilders, targetTypeRef.getName());
317 GeneratedTypeBuilder augTypeBuilder = new GeneratedTypeBuilderImpl(augmentPackageName, augTypeName);
319 augTypeBuilder.addImplementsType(BindingTypes.TREE_NODE);
320 augTypeBuilder.addImplementsType(Types.augmentationTypeFor(targetTypeRef));
321 annotateDeprecatedIfNecessary(augSchema.getStatus(), augTypeBuilder);
322 augTypeBuilder = addImplementedInterfaceFromUses(augSchema, augTypeBuilder, genCtx);
324 augTypeBuilder = augSchemaNodeToMethods(module, basePackageName, augTypeBuilder, augTypeBuilder, augSchema
326 augmentBuilders.put(augTypeName, augTypeBuilder);
328 if(!augSchema.getChildNodes().isEmpty()) {
329 genCtx.get(module).addTypeToAugmentation(augTypeBuilder, augSchema);
332 genCtx.get(module).addAugmentType(augTypeBuilder);
337 * Adds the methods to <code>typeBuilder</code> what represents subnodes of
338 * node for which <code>typeBuilder</code> was created.
342 * @param basePackageName
343 * string contains the module package name
345 * generated type builder which represents any node. The subnodes
346 * of this node are added to the <code>typeBuilder</code> as
347 * methods. The subnode can be of type leaf, leaf-list, list,
352 * set of data schema nodes which are the children of the node
353 * for which <code>typeBuilder</code> was created
354 * @return generated type builder which is the same object as the input
355 * parameter <code>typeBuilder</code>. The getter method could be
358 private static GeneratedTypeBuilder augSchemaNodeToMethods(final Module module, final String basePackageName,
359 final GeneratedTypeBuilder typeBuilder, final GeneratedTypeBuilder childOf,
360 final Iterable<DataSchemaNode> schemaNodes) {
361 if ((schemaNodes != null) && (typeBuilder != null)) {
362 for (final DataSchemaNode schemaNode : schemaNodes) {
363 if (!schemaNode.isAugmenting()) {
364 //TODO: design decomposition and implement it
365 //addSchemaNodeToBuilderAsMethod(basePackageName, schemaNode, typeBuilder, childOf, module);
373 * @param unknownSchemaNodes
374 * @return nodeParameter of UnknownSchemaNode
376 private static String getAugmentIdentifier(final List<UnknownSchemaNode> unknownSchemaNodes) {
377 for (final UnknownSchemaNode unknownSchemaNode : unknownSchemaNodes) {
378 final QName nodeType = unknownSchemaNode.getNodeType();
379 if (AUGMENT_IDENTIFIER_NAME.equals(nodeType.getLocalName())
380 && YANG_EXT_NAMESPACE.equals(nodeType.getNamespace().toString())) {
381 return unknownSchemaNode.getNodeParameter();
388 * Returns first unique name for the augment generated type builder. The
389 * generated type builder name for augment consists from name of augmented
390 * node and serial number of its augmentation.
393 * map of builders which were created in the package to which the
394 * augmentation belongs
396 * string with name of augmented node
397 * @return string with unique name for augmentation builder
399 private static String augGenTypeName(final Map<String, GeneratedTypeBuilder> builders, final String genTypeName) {
401 if (builders != null) {
402 while (builders.containsKey(genTypeName + index)) {
406 return genTypeName + index;
409 static GeneratedTypeBuilder addDefaultInterfaceDefinition(final String packageName, final SchemaNode
410 schemaNode, final Module module, Map<Module, ModuleContext> genCtx, final SchemaContext schemaContext,
411 final boolean verboseClassComments, Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders) {
412 return addDefaultInterfaceDefinition(packageName, schemaNode, null, module, genCtx, schemaContext,
413 verboseClassComments, genTypeBuilders);
417 * Instantiates generated type builder with <code>packageName</code> and
418 * <code>schemaNode</code>.
420 * The new builder always implements
421 * {@link TreeNode TreeNode}.<br>
422 * If <code>schemaNode</code> is instance of GroupingDefinition it also
423 * implements {@link Augmentable
425 * If <code>schemaNode</code> is instance of
426 * {@link org.opendaylight.yangtools.yang.model.api.DataNodeContainer
427 * DataNodeContainer} it can also implement nodes which are specified in
431 * string with the name of the package to which
432 * <code>schemaNode</code> belongs.
434 * schema node for which is created generated type builder
436 * parent type (can be null)
437 * @param schemaContext
438 * @return generated type builder <code>schemaNode</code>
440 private static GeneratedTypeBuilder addDefaultInterfaceDefinition(final String packageName, final SchemaNode
441 schemaNode, final Type parent, final Module module, Map<Module, ModuleContext> genCtx,
442 final SchemaContext schemaContext, final boolean verboseClassComments, Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders) {
443 GeneratedTypeBuilder it = addRawInterfaceDefinition(packageName, schemaNode, schemaContext, "",
444 verboseClassComments, genTypeBuilders);
445 if (parent == null) {
446 it.addImplementsType(BindingTypes.TREE_NODE);
448 it.addImplementsType(BindingTypes.treeChildNode(parent));
450 if (!(schemaNode instanceof GroupingDefinition)) {
451 it.addImplementsType(BindingTypes.augmentable(it));
454 if (schemaNode instanceof DataNodeContainer) {
455 //TODO: design decomposition and implement it
456 //groupingsToGenTypes(module, ((DataNodeContainer) schemaNode).getGroupings());
457 it = addImplementedInterfaceFromUses((DataNodeContainer) schemaNode, it, genCtx);
464 * Returns reference to generated type builder for specified
465 * <code>schemaNode</code> with <code>packageName</code>.
467 * Firstly the generated type builder is searched in
468 * {@link BindingGeneratorImpl#genTypeBuilders genTypeBuilders}. If it isn't
469 * found it is created and added to <code>genTypeBuilders</code>.
472 * string with the package name to which returning generated type
475 * schema node which provide data about the schema node name
476 * @param schemaContext
478 * return type name prefix
479 * @return generated type builder for <code>schemaNode</code>
480 * @throws IllegalArgumentException
482 * <li>if <code>schemaNode</code> is null</li>
483 * <li>if <code>packageName</code> is null</li>
484 * <li>if QName of schema node is null</li>
485 * <li>if schemaNode name is null</li>
489 private static GeneratedTypeBuilder addRawInterfaceDefinition(final String packageName, final SchemaNode schemaNode,
490 final SchemaContext schemaContext, final String prefix, final boolean verboseClassComments,
491 Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders) {
492 Preconditions.checkArgument(schemaNode != null, "Data Schema Node cannot be NULL.");
493 Preconditions.checkArgument(packageName != null, "Package Name for Generated Type cannot be NULL.");
494 Preconditions.checkArgument(schemaNode.getQName() != null, "QName for Data Schema Node cannot be NULL.");
495 final String schemaNodeName = schemaNode.getQName().getLocalName();
496 Preconditions.checkArgument(schemaNodeName != null, "Local Name of QName for Data Schema Node cannot be NULL.");
499 if (prefix == null) {
500 genTypeName = BindingMapping.getClassName(schemaNodeName);
502 genTypeName = prefix + BindingMapping.getClassName(schemaNodeName);
505 final GeneratedTypeBuilderImpl newType = new GeneratedTypeBuilderImpl(packageName, genTypeName);
506 final Module module = SchemaContextUtil.findParentModule(schemaContext, schemaNode);
507 qNameConstant(newType, BindingMapping.QNAME_STATIC_FIELD_NAME, schemaNode.getQName());
508 newType.addComment(schemaNode.getDescription());
509 newType.setDescription(createDescription(schemaNode, newType.getFullyQualifiedName(), schemaContext, verboseClassComments));
510 newType.setReference(schemaNode.getReference());
511 newType.setSchemaPath((List<QName>) schemaNode.getPath().getPathFromRoot());
512 newType.setModuleName(module.getName());
514 //FIXME: update genTypeBuilders for callers
515 if (!genTypeBuilders.containsKey(packageName)) {
516 final Map<String, GeneratedTypeBuilder> builders = new HashMap<>();
517 builders.put(genTypeName, newType);
518 genTypeBuilders.put(packageName, builders);
520 final Map<String, GeneratedTypeBuilder> builders = genTypeBuilders.get(packageName);
521 if (!builders.containsKey(genTypeName)) {
522 builders.put(genTypeName, newType);
529 private static Constant qNameConstant(final GeneratedTypeBuilderBase<?> toBuilder, final String constantName,
531 return toBuilder.addConstant(Types.typeForClass(QName.class), constantName, name);
534 private static String createDescription(final SchemaNode schemaNode, final String fullyQualifiedName,
535 final SchemaContext schemaContext, final boolean verboseClassComments) {
536 final StringBuilder sb = new StringBuilder();
537 final String nodeDescription = BindingGeneratorUtil.encodeAngleBrackets(schemaNode.getDescription());
538 final String formattedDescription = YangTextTemplate.formatToParagraph(nodeDescription, 0);
540 if (!Strings.isNullOrEmpty(formattedDescription)) {
541 sb.append(formattedDescription);
545 if (verboseClassComments) {
546 final Module module = SchemaContextUtil.findParentModule(schemaContext, schemaNode);
547 final StringBuilder linkToBuilderClass = new StringBuilder();
548 final String[] namespace = Iterables.toArray(BSDOT_SPLITTER.split(fullyQualifiedName), String.class);
549 final String className = namespace[namespace.length - 1];
551 if (hasBuilderClass(schemaNode)) {
552 linkToBuilderClass.append(className);
553 linkToBuilderClass.append("Builder");
557 sb.append("This class represents the following YANG schema fragment defined in module <b>");
558 sb.append(module.getName());
563 sb.append(BindingGeneratorUtil.encodeAngleBrackets(yangTemplateForNode.render(schemaNode).body()));
566 sb.append("The schema path to identify an instance is");
569 sb.append(YangTextTemplate.formatSchemaPath(module.getName(), schemaNode.getPath().getPathFromRoot()));
573 if (hasBuilderClass(schemaNode)) {
575 sb.append("<p>To create instances of this class use " + "{@link " + linkToBuilderClass + "}.");
578 sb.append(linkToBuilderClass);
580 if (schemaNode instanceof ListSchemaNode) {
581 final List<QName> keyDef = ((ListSchemaNode)schemaNode).getKeyDefinition();
582 if (keyDef != null && !keyDef.isEmpty()) {
584 sb.append(className);
592 return replaceAllIllegalChars(sb);
595 private static void annotateDeprecatedIfNecessary(final Status status, final GeneratedTypeBuilder builder) {
596 if (status == Status.DEPRECATED) {
597 builder.addAnnotation("", "Deprecated");
601 private static boolean hasBuilderClass(final SchemaNode schemaNode) {
602 if (schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode ||
603 schemaNode instanceof RpcDefinition || schemaNode instanceof NotificationDefinition) {