Bug 1411: BindingGeneratorImpl decomposition - Container schema nodes
[mdsal.git] / binding2 / mdsal-binding2-generator-impl / src / main / java / org / opendaylight / mdsal / binding / javav2 / generator / impl / GenHelperUtil.java
1 /*
2  * Copyright (c) 2017 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.binding.javav2.generator.impl;
10
11 import static org.opendaylight.mdsal.binding.javav2.generator.impl.AuxiliaryGenUtils.annotateDeprecatedIfNecessary;
12 import static org.opendaylight.mdsal.binding.javav2.generator.impl.AuxiliaryGenUtils.augGenTypeName;
13 import static org.opendaylight.mdsal.binding.javav2.generator.impl.AuxiliaryGenUtils.constructGetter;
14 import static org.opendaylight.mdsal.binding.javav2.generator.impl.AuxiliaryGenUtils.createDescription;
15 import static org.opendaylight.mdsal.binding.javav2.generator.impl.AuxiliaryGenUtils.getAugmentIdentifier;
16 import static org.opendaylight.mdsal.binding.javav2.generator.impl.AuxiliaryGenUtils.qNameConstant;
17 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingGeneratorUtil.packageNameForGeneratedType;
18
19 import com.google.common.annotations.Beta;
20 import com.google.common.annotations.VisibleForTesting;
21 import com.google.common.base.Preconditions;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.regex.Pattern;
26 import org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes;
27 import org.opendaylight.mdsal.binding.javav2.generator.util.JavaIdentifier;
28 import org.opendaylight.mdsal.binding.javav2.generator.util.NonJavaCharsConverter;
29 import org.opendaylight.mdsal.binding.javav2.generator.util.Types;
30 import org.opendaylight.mdsal.binding.javav2.generator.util.generated.type.builder.GeneratedTypeBuilderImpl;
31 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedType;
32 import org.opendaylight.mdsal.binding.javav2.model.api.Type;
33 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedTypeBuilder;
34 import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
35 import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingNamespaceType;
36 import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentable;
37 import org.opendaylight.mdsal.binding.javav2.util.BindingMapping;
38 import org.opendaylight.yangtools.yang.common.QName;
39 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
40 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
42 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
44 import org.opendaylight.yangtools.yang.model.api.Module;
45 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
46 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
47 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
48 import org.opendaylight.yangtools.yang.model.api.UsesNode;
49 import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
50
51 /**
52  * Helper util class used for generation of types in Binding spec v2.
53  */
54 @Beta
55 final class GenHelperUtil {
56
57     private GenHelperUtil() {
58         throw new UnsupportedOperationException("Util class");
59     }
60
61     /**
62      * Create GeneratedTypeBuilder object from module argument.
63      *
64      * @param module
65      *            Module object from which builder will be created
66      * @param genCtx
67      * @param verboseClassComments
68      *
69      * @return <code>GeneratedTypeBuilder</code> which is internal
70      *         representation of the module
71      * @throws IllegalArgumentException
72      *             if module is null
73      */
74     static GeneratedTypeBuilder moduleToDataType(final Module module, Map<Module, ModuleContext> genCtx, final boolean verboseClassComments) {
75         Preconditions.checkArgument(module != null, "Module reference cannot be NULL.");
76
77         final GeneratedTypeBuilder moduleDataTypeBuilder = moduleTypeBuilder(module, "Data", verboseClassComments);
78         addImplementedInterfaceFromUses(module, moduleDataTypeBuilder, genCtx);
79         moduleDataTypeBuilder.addImplementsType(BindingTypes.TREE_ROOT);
80         moduleDataTypeBuilder.addComment(module.getDescription());
81         moduleDataTypeBuilder.setDescription(createDescription(module, verboseClassComments));
82         moduleDataTypeBuilder.setReference(module.getReference());
83         return moduleDataTypeBuilder;
84     }
85
86     /**
87      * Generates type builder for <code>module</code>.
88      *
89      * @param module
90      *            Module which is source of package name for generated type
91      *            builder
92      * @param postfix
93      *            string which is added to the module class name representation
94      *            as suffix
95      * @param verboseClassComments
96      * @return instance of GeneratedTypeBuilder which represents
97      *         <code>module</code>.
98      * @throws IllegalArgumentException
99      *             if <code>module</code> is null
100      */
101     private static GeneratedTypeBuilder moduleTypeBuilder(final Module module, final String postfix, final boolean
102             verboseClassComments) {
103         Preconditions.checkArgument(module != null, "Module reference cannot be NULL.");
104         final String packageName = BindingMapping.getRootPackageName(module);
105         final String moduleName = BindingMapping.getClassName(NonJavaCharsConverter.convertIdentifier(module.getName
106                 (), JavaIdentifier.CLASS)) + postfix;
107
108         final GeneratedTypeBuilderImpl moduleBuilder = new GeneratedTypeBuilderImpl(packageName, moduleName);
109         moduleBuilder.setDescription(createDescription(module, verboseClassComments));
110         moduleBuilder.setReference(module.getReference());
111         moduleBuilder.setModuleName(moduleName);
112
113         return moduleBuilder;
114     }
115
116     /**
117      * Adds the implemented types to type builder.
118      *
119      * The method passes through the list of <i>uses</i> in
120      * {@code dataNodeContainer}. For every <i>use</i> is obtained corresponding
121      * generated type from all groupings
122      * allGroupings} which is added as <i>implements type</i> to
123      * <code>builder</code>
124      *
125      * @param dataNodeContainer
126      *            element which contains the list of used YANG groupings
127      * @param builder
128      *            builder to which are added implemented types according to
129      *            <code>dataNodeContainer</code>
130      * @param genCtx generated context
131      * @return generated type builder with all implemented types
132      */
133     private static GeneratedTypeBuilder addImplementedInterfaceFromUses(final DataNodeContainer dataNodeContainer,
134                           final GeneratedTypeBuilder builder, Map<Module, ModuleContext> genCtx) {
135         for (final UsesNode usesNode : dataNodeContainer.getUses()) {
136             final GeneratedType genType = findGroupingByPath(usesNode.getGroupingPath(), genCtx).toInstance();
137             if (genType == null) {
138                 throw new IllegalStateException("Grouping " + usesNode.getGroupingPath() + "is not resolved for "
139                     + builder.getName());
140             }
141             builder.addImplementsType(genType);
142         }
143         return builder;
144     }
145
146      static GeneratedTypeBuilder findGroupingByPath(final SchemaPath path, Map<Module, ModuleContext> genCtx) {
147         for (final ModuleContext ctx : genCtx.values()) {
148             final GeneratedTypeBuilder result = ctx.getGrouping(path);
149             if (result != null) {
150                 return result;
151             }
152         }
153         return null;
154      }
155
156     /**
157      * Adds the methods to <code>typeBuilder</code> which represent subnodes of
158      * node for which <code>typeBuilder</code> was created.
159      *
160      * The subnodes aren't mapped to the methods if they are part of grouping or
161      * augment (in this case are already part of them).
162      *
163      * @param module
164      *            current module
165      * @param basePackageName
166      *            string contains the module package name
167      * @param parent
168      *            generated type builder which represents any node. The subnodes
169      *            of this node are added to the <code>typeBuilder</code> as
170      *            methods. The subnode can be of type leaf, leaf-list, list,
171      *            container, choice.
172      * @param childOf
173      *            parent type
174      * @param schemaNodes
175      *            set of data schema nodes which are the children of the node
176      *            for which <code>typeBuilder</code> was created
177      * @return generated type builder which is the same builder as input
178      *         parameter. The getter methods (representing child nodes) could be
179      *         added to it.
180      */
181     static GeneratedTypeBuilder resolveDataSchemaNodes(final Module module, final String basePackageName,
182                           final GeneratedTypeBuilder parent, final GeneratedTypeBuilder childOf,
183                           final Iterable<DataSchemaNode> schemaNodes, Map<Module, ModuleContext> genCtx,
184                           final SchemaContext schemaContext, final boolean verboseClassComments,
185                           Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders) {
186
187         if (schemaNodes != null && parent != null) {
188             for (final DataSchemaNode schemaNode : schemaNodes) {
189                 if (!schemaNode.isAugmenting() && !schemaNode.isAddedByUses()) {
190                     addSchemaNodeToBuilderAsMethod(basePackageName, schemaNode, parent, childOf, module, genCtx,
191                             schemaContext, verboseClassComments, genTypeBuilders);
192                 }
193             }
194         }
195         return parent;
196     }
197
198     static GeneratedTypeBuilder addDefaultInterfaceDefinition(final String packageName, final SchemaNode
199             schemaNode, final Module module, Map<Module, ModuleContext> genCtx, final SchemaContext schemaContext,
200                                                               final boolean verboseClassComments, Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders) {
201         return addDefaultInterfaceDefinition(packageName, schemaNode, null, module, genCtx, schemaContext,
202                 verboseClassComments, genTypeBuilders);
203     }
204
205     static Map<Module, ModuleContext> processUsesAugments(final SchemaContext schemaContext, final
206                         DataNodeContainer node, final Module module, Map<Module, ModuleContext> genCtx,  Map<String,
207                         Map<String, GeneratedTypeBuilder>> genTypeBuilders, final boolean verboseClassComments) {
208         final String basePackageName = BindingMapping.getRootPackageName(module);
209         for (final UsesNode usesNode : node.getUses()) {
210             for (final AugmentationSchema augment : usesNode.getAugmentations()) {
211                 genCtx = AugmentToGenType.usesAugmentationToGenTypes(schemaContext, basePackageName, augment, module,
212                         usesNode,
213                         node, genCtx, genTypeBuilders, verboseClassComments);
214                 genCtx = processUsesAugments(schemaContext, augment, module, genCtx, genTypeBuilders, verboseClassComments);
215             }
216         }
217         return genCtx;
218     }
219
220     static GeneratedTypeBuilder findChildNodeByPath(final SchemaPath path, Map<Module, ModuleContext> genCtx) {
221         for (final ModuleContext ctx : genCtx.values()) {
222             final GeneratedTypeBuilder result = ctx.getChildNode(path);
223             if (result != null) {
224                 return result;
225             }
226         }
227         return null;
228     }
229
230     static GeneratedTypeBuilder findCaseByPath(final SchemaPath path, Map<Module, ModuleContext> genCtx) {
231         for (final ModuleContext ctx : genCtx.values()) {
232             final GeneratedTypeBuilder result = ctx.getCase(path);
233             if (result != null) {
234                 return result;
235             }
236         }
237         return null;
238     }
239
240     /**
241      * Returns a generated type builder for an augmentation.
242      *
243      * The name of the type builder is equal to the name of augmented node with
244      * serial number as suffix.
245      *
246      * @param module
247      *            current module
248      * @param augmentPackageName
249      *            string with contains the package name to which the augment
250      *            belongs
251      * @param basePackageName
252      *            string with the package name to which the augmented node
253      *            belongs
254      * @param targetTypeRef
255      *            target type
256      * @param augSchema
257      *            augmentation schema which contains data about the child nodes
258      *            and uses of augment
259      * @return generated type builder for augment in genCtx
260      */
261     static Map<Module, ModuleContext> addRawAugmentGenTypeDefinition(final Module module, final String augmentPackageName,
262                 final String basePackageName, final Type targetTypeRef, final AugmentationSchema augSchema,
263                 Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders, Map<Module, ModuleContext> genCtx) {
264
265         Map<String, GeneratedTypeBuilder> augmentBuilders = genTypeBuilders.get(augmentPackageName);
266         if (augmentBuilders == null) {
267             augmentBuilders = new HashMap<>();
268             genTypeBuilders.put(augmentPackageName, augmentBuilders);
269         }
270         final String augIdentifier = getAugmentIdentifier(augSchema.getUnknownSchemaNodes());
271
272         String augTypeName;
273         if (augIdentifier != null) {
274             augTypeName = BindingMapping.getClassName(augIdentifier);
275         } else {
276             augTypeName = augGenTypeName(augmentBuilders, targetTypeRef.getName());
277         }
278
279         GeneratedTypeBuilder augTypeBuilder = new GeneratedTypeBuilderImpl(augmentPackageName, augTypeName);
280
281         augTypeBuilder.addImplementsType(BindingTypes.TREE_NODE);
282         augTypeBuilder.addImplementsType(Types.augmentationTypeFor(targetTypeRef));
283         annotateDeprecatedIfNecessary(augSchema.getStatus(), augTypeBuilder);
284         augTypeBuilder = addImplementedInterfaceFromUses(augSchema, augTypeBuilder, genCtx);
285
286         augTypeBuilder = augSchemaNodeToMethods(module, basePackageName, augTypeBuilder, augTypeBuilder, augSchema
287                 .getChildNodes());
288         augmentBuilders.put(augTypeName, augTypeBuilder);
289
290         if(!augSchema.getChildNodes().isEmpty()) {
291             genCtx.get(module).addTypeToAugmentation(augTypeBuilder, augSchema);
292
293         }
294         genCtx.get(module).addAugmentType(augTypeBuilder);
295         return genCtx;
296     }
297
298     /**
299      * Adds the methods to <code>typeBuilder</code> what represents subnodes of
300      * node for which <code>typeBuilder</code> was created.
301      *
302      * @param module
303      *            current module
304      * @param basePackageName
305      *            string contains the module package name
306      * @param typeBuilder
307      *            generated type builder which represents any node. The subnodes
308      *            of this node are added to the <code>typeBuilder</code> as
309      *            methods. The subnode can be of type leaf, leaf-list, list,
310      *            container, choice.
311      * @param childOf
312      *            parent type
313      * @param schemaNodes
314      *            set of data schema nodes which are the children of the node
315      *            for which <code>typeBuilder</code> was created
316      * @return generated type builder which is the same object as the input
317      *         parameter <code>typeBuilder</code>. The getter method could be
318      *         added to it.
319      */
320     private static GeneratedTypeBuilder augSchemaNodeToMethods(final Module module, final String basePackageName,
321                                                         final GeneratedTypeBuilder typeBuilder, final GeneratedTypeBuilder childOf,
322                                                         final Iterable<DataSchemaNode> schemaNodes) {
323         if ((schemaNodes != null) && (typeBuilder != null)) {
324             for (final DataSchemaNode schemaNode : schemaNodes) {
325                 if (!schemaNode.isAugmenting()) {
326                     //TODO: design decomposition and implement it
327                     //addSchemaNodeToBuilderAsMethod(basePackageName, schemaNode, typeBuilder, childOf, module);
328                 }
329             }
330         }
331         return typeBuilder;
332     }
333
334     /**
335      * Instantiates generated type builder with <code>packageName</code> and
336      * <code>schemaNode</code>.
337      *
338      * The new builder always implements
339      * {@link TreeNode TreeNode}.<br>
340      * If <code>schemaNode</code> is instance of GroupingDefinition it also
341      * implements {@link Augmentable
342      * Augmentable}.<br>
343      * If <code>schemaNode</code> is instance of
344      * {@link org.opendaylight.yangtools.yang.model.api.DataNodeContainer
345      * DataNodeContainer} it can also implement nodes which are specified in
346      * <i>uses</i>.
347      *
348      * @param packageName
349      *            string with the name of the package to which
350      *            <code>schemaNode</code> belongs.
351      * @param schemaNode
352      *            schema node for which is created generated type builder
353      * @param parent
354      *            parent type (can be null)
355      * @param schemaContext schema context
356      * @return generated type builder <code>schemaNode</code>
357      */
358     private static GeneratedTypeBuilder addDefaultInterfaceDefinition(final String packageName, final SchemaNode
359             schemaNode, final Type parent, final Module module, Map<Module, ModuleContext> genCtx,
360             final SchemaContext schemaContext, final boolean verboseClassComments, Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders) {
361
362         GeneratedTypeBuilder it = addRawInterfaceDefinition(packageName, schemaNode, schemaContext, "",
363                 verboseClassComments, genTypeBuilders);
364         if (parent == null) {
365             it.addImplementsType(BindingTypes.TREE_NODE);
366         } else {
367             it.addImplementsType(BindingTypes.treeChildNode(parent));
368         }
369         if (!(schemaNode instanceof GroupingDefinition)) {
370             it.addImplementsType(BindingTypes.augmentable(it));
371         }
372
373         if (schemaNode instanceof DataNodeContainer) {
374             //TODO: design decomposition and implement it
375             //groupingsToGenTypes(module, ((DataNodeContainer) schemaNode).getGroupings());
376             it = addImplementedInterfaceFromUses((DataNodeContainer) schemaNode, it, genCtx);
377         }
378
379         return it;
380     }
381
382     /**
383      * Returns reference to generated type builder for specified
384      * <code>schemaNode</code> with <code>packageName</code>.
385      *
386      * Firstly the generated type builder is searched in
387      * {@link BindingGeneratorImpl#genTypeBuilders genTypeBuilders}. If it isn't
388      * found it is created and added to <code>genTypeBuilders</code>.
389      *
390      * @param packageName
391      *            string with the package name to which returning generated type
392      *            builder belongs
393      * @param schemaNode
394      *            schema node which provide data about the schema node name
395      * @param schemaContext
396      * @param prefix
397      *            return type name prefix
398      * @return generated type builder for <code>schemaNode</code>
399      * @throws IllegalArgumentException
400      *             <ul>
401      *             <li>if <code>schemaNode</code> is null</li>
402      *             <li>if <code>packageName</code> is null</li>
403      *             <li>if QName of schema node is null</li>
404      *             <li>if schemaNode name is null</li>
405      *             </ul>
406      *
407      */
408     private static GeneratedTypeBuilder addRawInterfaceDefinition(final String packageName, final SchemaNode schemaNode,
409                        final SchemaContext schemaContext, final String prefix, final boolean verboseClassComments,
410                        Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders) {
411
412         Preconditions.checkArgument(schemaNode != null, "Data Schema Node cannot be NULL.");
413         Preconditions.checkArgument(packageName != null, "Package Name for Generated Type cannot be NULL.");
414         final String schemaNodeName = schemaNode.getQName().getLocalName();
415         Preconditions.checkArgument(schemaNodeName != null, "Local Name of QName for Data Schema Node cannot be NULL.");
416
417         String genTypeName;
418         if (prefix == null) {
419             genTypeName = BindingMapping.getClassName(NonJavaCharsConverter.convertIdentifier(schemaNodeName,
420                     JavaIdentifier.CLASS));
421         } else {
422             genTypeName = prefix + BindingMapping.getClassName(NonJavaCharsConverter.convertIdentifier
423                     (schemaNodeName, JavaIdentifier.CLASS));
424         }
425
426         final GeneratedTypeBuilderImpl newType = new GeneratedTypeBuilderImpl(packageName, genTypeName);
427         final Module module = SchemaContextUtil.findParentModule(schemaContext, schemaNode);
428         qNameConstant(newType, BindingMapping.QNAME_STATIC_FIELD_NAME, schemaNode.getQName());
429         newType.addComment(schemaNode.getDescription());
430         newType.setDescription(createDescription(schemaNode, newType.getFullyQualifiedName(), schemaContext, verboseClassComments));
431         newType.setReference(schemaNode.getReference());
432         newType.setSchemaPath((List<QName>) schemaNode.getPath().getPathFromRoot());
433         newType.setModuleName(module.getName());
434
435         if (!genTypeBuilders.containsKey(packageName)) {
436             final Map<String, GeneratedTypeBuilder> builders = new HashMap<>();
437             builders.put(genTypeName, newType);
438             genTypeBuilders.put(packageName, builders);
439         } else {
440             final Map<String, GeneratedTypeBuilder> builders = genTypeBuilders.get(packageName);
441             if (!builders.containsKey(genTypeName)) {
442                 builders.put(genTypeName, newType);
443             }
444         }
445         return newType;
446
447     }
448
449     private static void addSchemaNodeToBuilderAsMethod(final String basePackageName, final DataSchemaNode node,
450         final GeneratedTypeBuilder typeBuilder, final GeneratedTypeBuilder childOf, final Module module,
451         Map<Module, ModuleContext> genCtx, final SchemaContext schemaContext, final boolean verboseClassComments,
452         Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders) {
453         //TODO: implement rest of schema nodes GTO building
454         if (node != null && typeBuilder != null) {
455             if (node instanceof ContainerSchemaNode) {
456                 containerToGenType(module, basePackageName, typeBuilder, childOf, (ContainerSchemaNode) node,
457                         schemaContext, verboseClassComments, genCtx, genTypeBuilders);
458             }
459         }
460
461     }
462
463     private static void containerToGenType(final Module module, final String basePackageName,
464         final GeneratedTypeBuilder parent, final GeneratedTypeBuilder childOf, final ContainerSchemaNode node,
465         final SchemaContext schemaContext, final boolean verboseClassComments, Map<Module, ModuleContext> genCtx,
466         Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders) {
467
468         final GeneratedTypeBuilder genType = processDataSchemaNode(module, basePackageName, childOf, node,
469                 schemaContext, verboseClassComments, genCtx, genTypeBuilders);
470         if (genType != null) {
471             constructGetter(parent, node.getQName().getLocalName(), node.getDescription(), genType, node.getStatus());
472             resolveDataSchemaNodes(module, basePackageName, genType, genType, node.getChildNodes(), genCtx,
473                     schemaContext, verboseClassComments, genTypeBuilders);
474         }
475     }
476
477     private static GeneratedTypeBuilder processDataSchemaNode(final Module module, final String basePackageName,
478         final GeneratedTypeBuilder childOf, final DataSchemaNode node, final SchemaContext schemaContext,
479         final boolean verboseClassComments, Map<Module, ModuleContext> genCtx, Map<String, Map<String,
480         GeneratedTypeBuilder>> genTypeBuilders) {
481
482         if (node.isAugmenting() || node.isAddedByUses()) {
483             return null;
484         }
485         final String packageName = packageNameForGeneratedType(basePackageName, node.getPath(), BindingNamespaceType.Data);
486         final GeneratedTypeBuilder genType = addDefaultInterfaceDefinition(packageName, node, childOf, module,
487                 genCtx, schemaContext, verboseClassComments, genTypeBuilders);
488         genType.addComment(node.getDescription());
489         annotateDeprecatedIfNecessary(node.getStatus(), genType);
490         genType.setDescription(createDescription(node, genType.getFullyQualifiedName(), schemaContext, verboseClassComments));
491         genType.setModuleName(module.getName());
492         genType.setReference(node.getReference());
493         genType.setSchemaPath((List) node.getPath().getPathFromRoot());
494         if (node instanceof DataNodeContainer) {
495             genCtx.get(module).addChildNodeType(node, genType);
496             //TODO: implement groupings to GTO building first
497             // groupingsToGenTypes(module, ((DataNodeContainer) node).getGroupings());
498             processUsesAugments(schemaContext, (DataNodeContainer) node, module, genCtx, genTypeBuilders,
499                     verboseClassComments);
500         }
501         return genType;
502     }
503
504 }