Bug 1459-1 - Re-organize mdsal-binding2-spec
[mdsal.git] / binding2 / mdsal-binding2-generator-impl / src / main / java / org / opendaylight / mdsal / binding2 / generator / impl / GenHelperUtil.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8
9 package org.opendaylight.mdsal.binding2.generator.impl;
10
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;
19 import java.util.Map;
20 import java.util.regex.Pattern;
21 import org.opendaylight.mdsal.binding2.generator.impl.util.YangTextTemplate;
22 import org.opendaylight.mdsal.binding2.generator.util.BindingGeneratorUtil;
23 import org.opendaylight.mdsal.binding2.generator.util.BindingTypes;
24 import org.opendaylight.mdsal.binding2.generator.util.Types;
25 import org.opendaylight.mdsal.binding2.generator.util.generated.type.builder.GeneratedTypeBuilderImpl;
26 import org.opendaylight.mdsal.binding2.model.api.Constant;
27 import org.opendaylight.mdsal.binding2.model.api.GeneratedType;
28 import org.opendaylight.mdsal.binding2.model.api.Type;
29 import org.opendaylight.mdsal.binding2.model.api.type.builder.GeneratedTypeBuilder;
30 import org.opendaylight.mdsal.binding2.model.api.type.builder.GeneratedTypeBuilderBase;
31 import org.opendaylight.mdsal.binding2.txt.yangTemplateForModule;
32 import org.opendaylight.mdsal.binding2.txt.yangTemplateForNode;
33 import org.opendaylight.mdsal.binding2.util.BindingMapping;
34 import org.opendaylight.yangtools.yang.common.QName;
35 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
36 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
38 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
40 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.Module;
42 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
43 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
44 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
45 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
47 import org.opendaylight.yangtools.yang.model.api.Status;
48 import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
49 import org.opendaylight.yangtools.yang.model.api.UsesNode;
50 import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
51
52 /**
53  * Helper util class used for generation of types in binding spec v2.
54  */
55 @Beta
56 final class GenHelperUtil {
57
58     private static final Pattern UNICODE_CHAR_PATTERN = Pattern.compile("\\\\+u");
59     private static final Splitter BSDOT_SPLITTER = Splitter.on("\\.");
60     private static final char NEW_LINE = '\n';
61
62     /**
63      * Constant with the concrete name of identifier.
64      */
65     private static final String AUGMENT_IDENTIFIER_NAME = "augment-identifier";
66
67     /**
68      * Constant with the concrete name of namespace.
69      */
70     private static final String YANG_EXT_NAMESPACE = "urn:opendaylight:yang:extension:yang-ext";
71
72     private GenHelperUtil() {
73         throw new UnsupportedOperationException("Util class");
74     }
75
76     /**
77      * Create GeneratedTypeBuilder object from module argument.
78      *
79      * @param module
80      *            Module object from which builder will be created
81      * @param genCtx
82      * @param verboseClassComments
83      *
84      * @return <code>GeneratedTypeBuilder</code> which is internal
85      *         representation of the module
86      * @throws IllegalArgumentException
87      *             if module is null
88      */
89     static GeneratedTypeBuilder moduleToDataType(final Module module, Map<Module, ModuleContext> genCtx, final boolean verboseClassComments) {
90         Preconditions.checkArgument(module != null, "Module reference cannot be NULL.");
91
92         final GeneratedTypeBuilder moduleDataTypeBuilder = moduleTypeBuilder(module, "Data", verboseClassComments);
93         addImplementedInterfaceFromUses(module, moduleDataTypeBuilder, genCtx);
94         moduleDataTypeBuilder.addImplementsType(BindingTypes.TREE_ROOT);
95         moduleDataTypeBuilder.addComment(module.getDescription());
96         moduleDataTypeBuilder.setDescription(createDescription(module, verboseClassComments));
97         moduleDataTypeBuilder.setReference(module.getReference());
98         return moduleDataTypeBuilder;
99     }
100
101     /**
102      * Generates type builder for <code>module</code>.
103      *
104      * @param module
105      *            Module which is source of package name for generated type
106      *            builder
107      * @param postfix
108      *            string which is added to the module class name representation
109      *            as suffix
110      * @param verboseClassComments
111      * @return instance of GeneratedTypeBuilder which represents
112      *         <code>module</code>.
113      * @throws IllegalArgumentException
114      *             if <code>module</code> is null
115      */
116     static GeneratedTypeBuilder moduleTypeBuilder(final Module module, final String postfix, final boolean verboseClassComments) {
117         Preconditions.checkArgument(module != null, "Module reference cannot be NULL.");
118         final String packageName = BindingMapping.getRootPackageName(module);
119         final String moduleName = BindingMapping.getClassName(module.getName()) + postfix;
120
121         final GeneratedTypeBuilderImpl moduleBuilder = new GeneratedTypeBuilderImpl(packageName, moduleName);
122         moduleBuilder.setDescription(createDescription(module, verboseClassComments));
123         moduleBuilder.setReference(module.getReference());
124         moduleBuilder.setModuleName(moduleName);
125
126         return moduleBuilder;
127     }
128
129     /**
130      * Adds the implemented types to type builder.
131      *
132      * The method passes through the list of <i>uses</i> in
133      * {@code dataNodeContainer}. For every <i>use</i> is obtained corresponding
134      * generated type from all groupings
135      * allGroupings} which is added as <i>implements type</i> to
136      * <code>builder</code>
137      *
138      * @param dataNodeContainer
139      *            element which contains the list of used YANG groupings
140      * @param builder
141      *            builder to which are added implemented types according to
142      *            <code>dataNodeContainer</code>
143      * @param genCtx
144      * @return generated type builder with all implemented types
145      */
146     private static GeneratedTypeBuilder addImplementedInterfaceFromUses(final DataNodeContainer dataNodeContainer,
147                           final GeneratedTypeBuilder builder, Map<Module, ModuleContext> genCtx) {
148         for (final UsesNode usesNode : dataNodeContainer.getUses()) {
149             if (usesNode.getGroupingPath() != null) {
150                 final GeneratedType genType = findGroupingByPath(usesNode.getGroupingPath(), genCtx).toInstance();
151                 if (genType == null) {
152                     throw new IllegalStateException("Grouping " + usesNode.getGroupingPath() + "is not resolved for "
153                             + builder.getName());
154                 }
155
156                 builder.addImplementsType(genType);
157             }
158         }
159         return builder;
160     }
161
162      static GeneratedTypeBuilder findGroupingByPath(final SchemaPath path, Map<Module, ModuleContext> genCtx) {
163         for (final ModuleContext ctx : genCtx.values()) {
164             final GeneratedTypeBuilder result = ctx.getGrouping(path);
165             if (result != null) {
166                 return result;
167             }
168         }
169         return null;
170      }
171
172     private static String createDescription(final Module module, final boolean verboseClassComments) {
173         final StringBuilder sb = new StringBuilder();
174         final String moduleDescription = BindingGeneratorUtil.encodeAngleBrackets(module.getDescription());
175         final String formattedDescription = YangTextTemplate.formatToParagraph(moduleDescription, 0);
176
177         if (!Strings.isNullOrEmpty(formattedDescription)) {
178             sb.append(formattedDescription);
179             sb.append(NEW_LINE);
180         }
181
182         if (verboseClassComments) {
183             sb.append("<p>");
184             sb.append("This class represents the following YANG schema fragment defined in module <b>");
185             sb.append(module.getName());
186             sb.append("</b>");
187             sb.append(NEW_LINE);
188             sb.append("<pre>");
189             sb.append(NEW_LINE);
190             sb.append(BindingGeneratorUtil.encodeAngleBrackets(yangTemplateForModule.render(module).body()));
191             sb.append("</pre>");
192         }
193
194         return replaceAllIllegalChars(sb);
195     }
196
197     @VisibleForTesting
198     public static String replaceAllIllegalChars(final StringBuilder stringBuilder){
199         final String ret = UNICODE_CHAR_PATTERN.matcher(stringBuilder).replaceAll("\\\\\\\\u");
200         return ret.isEmpty() ? "" : ret;
201     }
202
203     /**
204      * Adds the methods to <code>typeBuilder</code> which represent subnodes of
205      * node for which <code>typeBuilder</code> was created.
206      *
207      * The subnodes aren't mapped to the methods if they are part of grouping or
208      * augment (in this case are already part of them).
209      *
210      * @param module
211      *            current module
212      * @param basePackageName
213      *            string contains the module package name
214      * @param parent
215      *            generated type builder which represents any node. The subnodes
216      *            of this node are added to the <code>typeBuilder</code> as
217      *            methods. The subnode can be of type leaf, leaf-list, list,
218      *            container, choice.
219      * @param childOf
220      *            parent type
221      * @param schemaNodes
222      *            set of data schema nodes which are the children of the node
223      *            for which <code>typeBuilder</code> was created
224      * @return generated type builder which is the same builder as input
225      *         parameter. The getter methods (representing child nodes) could be
226      *         added to it.
227      */
228     static GeneratedTypeBuilder resolveDataSchemaNodes(final Module module, final String basePackageName,
229                           final GeneratedTypeBuilder parent, final GeneratedTypeBuilder childOf, final Iterable<DataSchemaNode> schemaNodes) {
230         if (schemaNodes != null && parent != null) {
231             for (final DataSchemaNode schemaNode : schemaNodes) {
232                 if (!schemaNode.isAugmenting() && !schemaNode.isAddedByUses()) {
233                     //TODO: design decomposition and implement it
234                     //addSchemaNodeToBuilderAsMethod(basePackageName, schemaNode, parent, childOf, module);
235                 }
236             }
237         }
238         return parent;
239     }
240
241     static Map<Module, ModuleContext> processUsesAugments(final SchemaContext schemaContext, final
242                         DataNodeContainer node, final Module module, Map<Module, ModuleContext> genCtx,  Map<String,
243                         Map<String, GeneratedTypeBuilder>> genTypeBuilders, final boolean verboseClassComments) {
244         final String basePackageName = BindingMapping.getRootPackageName(module);
245         for (final UsesNode usesNode : node.getUses()) {
246             for (final AugmentationSchema augment : usesNode.getAugmentations()) {
247                 genCtx = AugmentToGenType.usesAugmentationToGenTypes(schemaContext, basePackageName, augment, module,
248                         usesNode,
249                         node, genCtx, genTypeBuilders, verboseClassComments);
250                 genCtx = processUsesAugments(schemaContext, augment, module, genCtx, genTypeBuilders, verboseClassComments);
251             }
252         }
253         return genCtx;
254     }
255
256     static GeneratedTypeBuilder findChildNodeByPath(final SchemaPath path, Map<Module, ModuleContext> genCtx) {
257         for (final ModuleContext ctx : genCtx.values()) {
258             final GeneratedTypeBuilder result = ctx.getChildNode(path);
259             if (result != null) {
260                 return result;
261             }
262         }
263         return null;
264     }
265
266     static GeneratedTypeBuilder findCaseByPath(final SchemaPath path, Map<Module, ModuleContext> genCtx) {
267         for (final ModuleContext ctx : genCtx.values()) {
268             final GeneratedTypeBuilder result = ctx.getCase(path);
269             if (result != null) {
270                 return result;
271             }
272         }
273         return null;
274     }
275
276     /**
277      * Returns a generated type builder for an augmentation.
278      *
279      * The name of the type builder is equal to the name of augmented node with
280      * serial number as suffix.
281      *
282      * @param module
283      *            current module
284      * @param augmentPackageName
285      *            string with contains the package name to which the augment
286      *            belongs
287      * @param basePackageName
288      *            string with the package name to which the augmented node
289      *            belongs
290      * @param targetTypeRef
291      *            target type
292      * @param augSchema
293      *            augmentation schema which contains data about the child nodes
294      *            and uses of augment
295      * @return generated type builder for augment in genCtx
296      */
297     static Map<Module, ModuleContext> addRawAugmentGenTypeDefinition(final Module module, final String augmentPackageName,
298                 final String basePackageName, final Type targetTypeRef, final AugmentationSchema augSchema,
299                 Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders, Map<Module, ModuleContext> genCtx) {
300
301         Map<String, GeneratedTypeBuilder> augmentBuilders = genTypeBuilders.get(augmentPackageName);
302         if (augmentBuilders == null) {
303             augmentBuilders = new HashMap<>();
304             genTypeBuilders.put(augmentPackageName, augmentBuilders);
305         }
306         final String augIdentifier = getAugmentIdentifier(augSchema.getUnknownSchemaNodes());
307
308         String augTypeName;
309         if (augIdentifier != null) {
310             augTypeName = BindingMapping.getClassName(augIdentifier);
311         } else {
312             augTypeName = augGenTypeName(augmentBuilders, targetTypeRef.getName());
313         }
314
315         GeneratedTypeBuilder augTypeBuilder = new GeneratedTypeBuilderImpl(augmentPackageName, augTypeName);
316
317         augTypeBuilder.addImplementsType(BindingTypes.TREE_NODE);
318         augTypeBuilder.addImplementsType(Types.augmentationTypeFor(targetTypeRef));
319         annotateDeprecatedIfNecessary(augSchema.getStatus(), augTypeBuilder);
320         augTypeBuilder = addImplementedInterfaceFromUses(augSchema, augTypeBuilder, genCtx);
321
322         augTypeBuilder = augSchemaNodeToMethods(module, basePackageName, augTypeBuilder, augTypeBuilder, augSchema
323                 .getChildNodes());
324         augmentBuilders.put(augTypeName, augTypeBuilder);
325
326         if(!augSchema.getChildNodes().isEmpty()) {
327             genCtx.get(module).addTypeToAugmentation(augTypeBuilder, augSchema);
328
329         }
330         genCtx.get(module).addAugmentType(augTypeBuilder);
331         return genCtx;
332     }
333
334     /**
335      * Adds the methods to <code>typeBuilder</code> what represents subnodes of
336      * node for which <code>typeBuilder</code> was created.
337      *
338      * @param module
339      *            current module
340      * @param basePackageName
341      *            string contains the module package name
342      * @param typeBuilder
343      *            generated type builder which represents any node. The subnodes
344      *            of this node are added to the <code>typeBuilder</code> as
345      *            methods. The subnode can be of type leaf, leaf-list, list,
346      *            container, choice.
347      * @param childOf
348      *            parent type
349      * @param schemaNodes
350      *            set of data schema nodes which are the children of the node
351      *            for which <code>typeBuilder</code> was created
352      * @return generated type builder which is the same object as the input
353      *         parameter <code>typeBuilder</code>. The getter method could be
354      *         added to it.
355      */
356     private static GeneratedTypeBuilder augSchemaNodeToMethods(final Module module, final String basePackageName,
357                                                         final GeneratedTypeBuilder typeBuilder, final GeneratedTypeBuilder childOf,
358                                                         final Iterable<DataSchemaNode> schemaNodes) {
359         if ((schemaNodes != null) && (typeBuilder != null)) {
360             for (final DataSchemaNode schemaNode : schemaNodes) {
361                 if (!schemaNode.isAugmenting()) {
362                     //TODO: design decomposition and implement it
363                     //addSchemaNodeToBuilderAsMethod(basePackageName, schemaNode, typeBuilder, childOf, module);
364                 }
365             }
366         }
367         return typeBuilder;
368     }
369
370     /**
371      * @param unknownSchemaNodes
372      * @return nodeParameter of UnknownSchemaNode
373      */
374     private static String getAugmentIdentifier(final List<UnknownSchemaNode> unknownSchemaNodes) {
375         for (final UnknownSchemaNode unknownSchemaNode : unknownSchemaNodes) {
376             final QName nodeType = unknownSchemaNode.getNodeType();
377             if (AUGMENT_IDENTIFIER_NAME.equals(nodeType.getLocalName())
378                     && YANG_EXT_NAMESPACE.equals(nodeType.getNamespace().toString())) {
379                 return unknownSchemaNode.getNodeParameter();
380             }
381         }
382         return null;
383     }
384
385     /**
386      * Returns first unique name for the augment generated type builder. The
387      * generated type builder name for augment consists from name of augmented
388      * node and serial number of its augmentation.
389      *
390      * @param builders
391      *            map of builders which were created in the package to which the
392      *            augmentation belongs
393      * @param genTypeName
394      *            string with name of augmented node
395      * @return string with unique name for augmentation builder
396      */
397     private static String augGenTypeName(final Map<String, GeneratedTypeBuilder> builders, final String genTypeName) {
398         int index = 1;
399         if (builders != null) {
400             while (builders.containsKey(genTypeName + index)) {
401                 index = index + 1;
402             }
403         }
404         return genTypeName + index;
405     }
406
407     static GeneratedTypeBuilder addDefaultInterfaceDefinition(final String packageName, final SchemaNode
408             schemaNode, final Module module, Map<Module, ModuleContext> genCtx, final SchemaContext schemaContext,
409             final boolean verboseClassComments, Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders) {
410         return addDefaultInterfaceDefinition(packageName, schemaNode, null, module, genCtx, schemaContext,
411                 verboseClassComments, genTypeBuilders);
412     }
413
414     /**
415      * Instantiates generated type builder with <code>packageName</code> and
416      * <code>schemaNode</code>.
417      *
418      * The new builder always implements
419      * {@link org.opendaylight.mdsal.binding2.spec.TreeNode TreeNode}.<br>
420      * If <code>schemaNode</code> is instance of GroupingDefinition it also
421      * implements {@link org.opendaylight.mdsal.binding2.spec.Augmentable
422      * Augmentable}.<br>
423      * If <code>schemaNode</code> is instance of
424      * {@link org.opendaylight.yangtools.yang.model.api.DataNodeContainer
425      * DataNodeContainer} it can also implement nodes which are specified in
426      * <i>uses</i>.
427      *
428      * @param packageName
429      *            string with the name of the package to which
430      *            <code>schemaNode</code> belongs.
431      * @param schemaNode
432      *            schema node for which is created generated type builder
433      * @param parent
434      *            parent type (can be null)
435      * @param schemaContext
436      * @return generated type builder <code>schemaNode</code>
437      */
438     private static GeneratedTypeBuilder addDefaultInterfaceDefinition(final String packageName, final SchemaNode
439             schemaNode, final Type parent, final Module module, Map<Module, ModuleContext> genCtx,
440             final SchemaContext schemaContext, final boolean verboseClassComments, Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders) {
441         GeneratedTypeBuilder it = addRawInterfaceDefinition(packageName, schemaNode, schemaContext, "",
442                 verboseClassComments, genTypeBuilders);
443         if (parent == null) {
444             it.addImplementsType(BindingTypes.TREE_NODE);
445         } else {
446             it.addImplementsType(BindingTypes.treeChildNode(parent));
447         }
448         if (!(schemaNode instanceof GroupingDefinition)) {
449             it.addImplementsType(BindingTypes.augmentable(it));
450         }
451
452         if (schemaNode instanceof DataNodeContainer) {
453             //TODO: design decomposition and implement it
454             //groupingsToGenTypes(module, ((DataNodeContainer) schemaNode).getGroupings());
455             it = addImplementedInterfaceFromUses((DataNodeContainer) schemaNode, it, genCtx);
456         }
457
458         return it;
459     }
460
461     /**
462      * Returns reference to generated type builder for specified
463      * <code>schemaNode</code> with <code>packageName</code>.
464      *
465      * Firstly the generated type builder is searched in
466      * {@link BindingGeneratorImpl#genTypeBuilders genTypeBuilders}. If it isn't
467      * found it is created and added to <code>genTypeBuilders</code>.
468      *
469      * @param packageName
470      *            string with the package name to which returning generated type
471      *            builder belongs
472      * @param schemaNode
473      *            schema node which provide data about the schema node name
474      * @param schemaContext
475      * @param prefix
476      *            return type name prefix
477      * @return generated type builder for <code>schemaNode</code>
478      * @throws IllegalArgumentException
479      *             <ul>
480      *             <li>if <code>schemaNode</code> is null</li>
481      *             <li>if <code>packageName</code> is null</li>
482      *             <li>if QName of schema node is null</li>
483      *             <li>if schemaNode name is null</li>
484      *             </ul>
485      *
486      */
487     private static GeneratedTypeBuilder addRawInterfaceDefinition(final String packageName, final SchemaNode schemaNode,
488                        final SchemaContext schemaContext, final String prefix, final boolean verboseClassComments,
489                        Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders) {
490         Preconditions.checkArgument(schemaNode != null, "Data Schema Node cannot be NULL.");
491         Preconditions.checkArgument(packageName != null, "Package Name for Generated Type cannot be NULL.");
492         Preconditions.checkArgument(schemaNode.getQName() != null, "QName for Data Schema Node cannot be NULL.");
493         final String schemaNodeName = schemaNode.getQName().getLocalName();
494         Preconditions.checkArgument(schemaNodeName != null, "Local Name of QName for Data Schema Node cannot be NULL.");
495
496         String genTypeName;
497         if (prefix == null) {
498             genTypeName = BindingMapping.getClassName(schemaNodeName);
499         } else {
500             genTypeName = prefix + BindingMapping.getClassName(schemaNodeName);
501         }
502
503         final GeneratedTypeBuilderImpl newType = new GeneratedTypeBuilderImpl(packageName, genTypeName);
504         final Module module = SchemaContextUtil.findParentModule(schemaContext, schemaNode);
505         qNameConstant(newType, BindingMapping.QNAME_STATIC_FIELD_NAME, schemaNode.getQName());
506         newType.addComment(schemaNode.getDescription());
507         newType.setDescription(createDescription(schemaNode, newType.getFullyQualifiedName(), schemaContext, verboseClassComments));
508         newType.setReference(schemaNode.getReference());
509         newType.setSchemaPath((List<QName>) schemaNode.getPath().getPathFromRoot());
510         newType.setModuleName(module.getName());
511
512         //FIXME: update genTypeBuilders for callers
513         if (!genTypeBuilders.containsKey(packageName)) {
514             final Map<String, GeneratedTypeBuilder> builders = new HashMap<>();
515             builders.put(genTypeName, newType);
516             genTypeBuilders.put(packageName, builders);
517         } else {
518             final Map<String, GeneratedTypeBuilder> builders = genTypeBuilders.get(packageName);
519             if (!builders.containsKey(genTypeName)) {
520                 builders.put(genTypeName, newType);
521             }
522         }
523         return newType;
524
525     }
526
527     private static Constant qNameConstant(final GeneratedTypeBuilderBase<?> toBuilder, final String constantName,
528                                           final QName name) {
529         return toBuilder.addConstant(Types.typeForClass(QName.class), constantName, name);
530     }
531
532     private static String createDescription(final SchemaNode schemaNode, final String fullyQualifiedName,
533                              final SchemaContext schemaContext, final boolean verboseClassComments) {
534         final StringBuilder sb = new StringBuilder();
535         final String nodeDescription = BindingGeneratorUtil.encodeAngleBrackets(schemaNode.getDescription());
536         final String formattedDescription = YangTextTemplate.formatToParagraph(nodeDescription, 0);
537
538         if (!Strings.isNullOrEmpty(formattedDescription)) {
539             sb.append(formattedDescription);
540             sb.append(NEW_LINE);
541         }
542
543         if (verboseClassComments) {
544             final Module module = SchemaContextUtil.findParentModule(schemaContext, schemaNode);
545             final StringBuilder linkToBuilderClass = new StringBuilder();
546             final String[] namespace = Iterables.toArray(BSDOT_SPLITTER.split(fullyQualifiedName), String.class);
547             final String className = namespace[namespace.length - 1];
548
549             if (hasBuilderClass(schemaNode)) {
550                 linkToBuilderClass.append(className);
551                 linkToBuilderClass.append("Builder");
552             }
553
554             sb.append("<p>");
555             sb.append("This class represents the following YANG schema fragment defined in module <b>");
556             sb.append(module.getName());
557             sb.append("</b>");
558             sb.append(NEW_LINE);
559             sb.append("<pre>");
560             sb.append(NEW_LINE);
561             sb.append(BindingGeneratorUtil.encodeAngleBrackets(yangTemplateForNode.render(schemaNode).body()));
562             sb.append("</pre>");
563             sb.append(NEW_LINE);
564             sb.append("The schema path to identify an instance is");
565             sb.append(NEW_LINE);
566             sb.append("<i>");
567             sb.append(YangTextTemplate.formatSchemaPath(module.getName(), schemaNode.getPath().getPathFromRoot()));
568             sb.append("</i>");
569             sb.append(NEW_LINE);
570
571             if (hasBuilderClass(schemaNode)) {
572                 sb.append(NEW_LINE);
573                 sb.append("<p>To create instances of this class use " + "{@link " + linkToBuilderClass + "}.");
574                 sb.append(NEW_LINE);
575                 sb.append("@see ");
576                 sb.append(linkToBuilderClass);
577                 sb.append(NEW_LINE);
578                 if (schemaNode instanceof ListSchemaNode) {
579                     final List<QName> keyDef = ((ListSchemaNode)schemaNode).getKeyDefinition();
580                     if (keyDef != null && !keyDef.isEmpty()) {
581                         sb.append("@see ");
582                         sb.append(className);
583                         sb.append("Key");
584                     }
585                     sb.append(NEW_LINE);
586                 }
587             }
588         }
589
590         return replaceAllIllegalChars(sb);
591     }
592
593     private static void annotateDeprecatedIfNecessary(final Status status, final GeneratedTypeBuilder builder) {
594         if (status == Status.DEPRECATED) {
595             builder.addAnnotation("", "Deprecated");
596         }
597     }
598
599     private static boolean hasBuilderClass(final SchemaNode schemaNode) {
600         if (schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode ||
601                 schemaNode instanceof RpcDefinition || schemaNode instanceof NotificationDefinition) {
602             return true;
603         }
604         return false;
605     }
606
607 }