Binding generator v2 - augments fix #2
[mdsal.git] / binding2 / mdsal-binding2-generator-impl / src / main / java / org / opendaylight / mdsal / binding / javav2 / generator / impl / AugmentToGenType.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 com.google.common.annotations.Beta;
12 import com.google.common.base.Optional;
13 import com.google.common.base.Preconditions;
14 import java.util.ArrayList;
15 import java.util.Collections;
16 import java.util.Comparator;
17 import java.util.Iterator;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Map.Entry;
21 import java.util.Set;
22 import java.util.stream.Collectors;
23 import org.opendaylight.mdsal.binding.javav2.generator.spi.TypeProvider;
24 import org.opendaylight.mdsal.binding.javav2.generator.util.BindingGeneratorUtil;
25 import org.opendaylight.mdsal.binding.javav2.model.api.Type;
26 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedTypeBuilder;
27 import org.opendaylight.mdsal.binding.javav2.util.BindingMapping;
28 import org.opendaylight.yangtools.yang.common.QName;
29 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
30 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
31 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
33 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.DerivableSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
36 import org.opendaylight.yangtools.yang.model.api.Module;
37 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
38 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
39 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
41 import org.opendaylight.yangtools.yang.model.api.UsesNode;
42 import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
43
44 @Beta
45 final class AugmentToGenType {
46
47     /**
48      * Comparator based on augment target path.
49      */
50     private static final Comparator<AugmentationSchema> AUGMENT_COMP = (o1, o2) -> {
51         final Iterator<QName> thisIt = o1.getTargetPath().getPathFromRoot().iterator();
52         final Iterator<QName> otherIt = o2.getTargetPath().getPathFromRoot().iterator();
53
54         while (thisIt.hasNext()) {
55             if (!otherIt.hasNext()) {
56                 return 1;
57             }
58
59             final int comp = thisIt.next().compareTo(otherIt.next());
60             if (comp != 0) {
61                 return comp;
62             }
63         }
64
65         return otherIt.hasNext() ? -1 : 0;
66     };
67
68     private AugmentToGenType() {
69         throw new UnsupportedOperationException("Utility class");
70     }
71
72     /**
73      * Converts all <b>augmentation</b> of the module to the list
74      * <code>Type</code> objects.
75      *
76      * @param module
77      *            module from which is obtained list of all augmentation objects
78      *            to iterate over them
79      * @param schemaContext actual schema context
80      * @param typeProvider actual type provider instance
81      * @param genCtx generated input context
82      * @param genTypeBuilders auxiliary type builders map
83      * @param verboseClassComments verbosity switch
84      *
85      * @throws IllegalArgumentException
86      *             <ul>
87      *             <li>if the module is null</li>
88      *             <li>if the name of module is null</li>
89      *             </ul>
90      * @throws IllegalStateException
91      *             if set of augmentations from module is null
92      */
93     static Map<Module, ModuleContext> generate(final Module module, final SchemaContext schemaContext,
94             final TypeProvider typeProvider, final Map<Module, ModuleContext> genCtx,
95             Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders, final boolean verboseClassComments) {
96
97         Preconditions.checkArgument(module != null, "Module reference cannot be NULL.");
98         Preconditions.checkArgument(module.getName() != null, "Module name cannot be NULL.");
99         Preconditions.checkState(module.getAugmentations() != null, "Augmentations Set cannot be NULL.");
100
101         final String basePackageName = BindingMapping.getRootPackageName(module);
102         final List<AugmentationSchema> augmentations = resolveAugmentations(module);
103         Map<Module, ModuleContext> resultCtx = genCtx;
104
105         //let's group augments by target path
106         Map<SchemaPath, List<AugmentationSchema>> augmentationsGrouped =
107                 augmentations.stream().collect(Collectors.groupingBy(AugmentationSchema::getTargetPath));
108
109         //process child nodes of grouped augment entries
110         for (Entry<SchemaPath, List<AugmentationSchema>> schemaPathAugmentListEntry : augmentationsGrouped.entrySet()) {
111             resultCtx = augmentationToGenTypes(basePackageName, schemaPathAugmentListEntry, module, schemaContext,
112                     verboseClassComments, resultCtx, genTypeBuilders, typeProvider);
113
114             for (AugmentationSchema augSchema : schemaPathAugmentListEntry.getValue()) {
115                 GenHelperUtil.processUsesAugments(schemaContext, augSchema, module, genCtx,
116                         genTypeBuilders, verboseClassComments, typeProvider);
117             }
118
119         }
120
121         return resultCtx;
122     }
123
124     /**
125      * Returns list of <code>AugmentationSchema</code> objects. The objects are
126      * sorted according to the length of their target path from the shortest to
127      * the longest.
128      *
129      * @param module
130      *            module from which is obtained list of all augmentation objects
131      * @return list of sorted <code>AugmentationSchema</code> objects obtained
132      *         from <code>module</code>
133      * @throws IllegalArgumentException
134      *             if module is null
135      * @throws IllegalStateException
136      *             if set of module augmentations is null
137      */
138     private static List<AugmentationSchema> resolveAugmentations(final Module module) {
139         Preconditions.checkArgument(module != null, "Module reference cannot be NULL.");
140         Preconditions.checkState(module.getAugmentations() != null, "Augmentations Set cannot be NULL.");
141
142         final Set<AugmentationSchema> augmentations = module.getAugmentations();
143         final List<AugmentationSchema> sortedAugmentations = new ArrayList<>(augmentations);
144         Collections.sort(sortedAugmentations, AUGMENT_COMP);
145
146         return sortedAugmentations;
147     }
148
149     /**
150      * Converts <code>augSchema</code> to list of <code>Type</code> which
151      * contains generated type for augmentation. In addition there are also
152      * generated types for all containers, list and choices which are child of
153      * <code>augSchema</code> node or a generated types for cases are added if
154      * augmented node is choice.
155      *
156      * @param augmentPackageName
157      *            string with the name of the package to which the augmentation
158      *            belongs
159      * @param schemaPathAugmentListEntry
160      *            list of AugmentationSchema nodes grouped by target path
161      * @param module current module
162      * @param schemaContext actual schema context
163      * @param verboseClassComments verbosity switch
164      * @param genCtx generated input context
165      * @param genTypeBuilders auxiliary type builders map
166      * @param typeProvider actual type provider instance
167      * @throws IllegalArgumentException
168      *             if <code>augmentPackageName</code> equals null
169      * @throws IllegalStateException
170      *             if augment target path is null
171      * @return generated context
172      */
173     private static Map<Module, ModuleContext> augmentationToGenTypes(final String basePackageName,
174             final Entry<SchemaPath, List<AugmentationSchema>> schemaPathAugmentListEntry, final Module module,
175             final SchemaContext schemaContext, final boolean verboseClassComments,
176             Map<Module, ModuleContext> genCtx, Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders,
177             final TypeProvider typeProvider) {
178
179         final SchemaPath targetPath = schemaPathAugmentListEntry.getKey();
180         Preconditions.checkArgument(basePackageName != null, "Package Name cannot be NULL.");
181         Preconditions.checkState(targetPath != null,
182                 "Augmentation Schema does not contain Target Path (Target Path is NULL).");
183
184         SchemaNode targetSchemaNode;
185
186         targetSchemaNode = SchemaContextUtil.findDataSchemaNode(schemaContext, targetPath);
187         if (targetSchemaNode instanceof DataSchemaNode && ((DataSchemaNode) targetSchemaNode).isAddedByUses()) {
188             if (targetSchemaNode instanceof DerivableSchemaNode) {
189                 targetSchemaNode = ((DerivableSchemaNode) targetSchemaNode).getOriginal().orNull();
190             }
191             if (targetSchemaNode == null) {
192                 throw new IllegalStateException("Failed to find target node from grouping in augmentation " +
193                         schemaPathAugmentListEntry.getValue().get(0)
194                         + " in module " + module.getName());
195             }
196         }
197         if (targetSchemaNode == null) {
198             throw new IllegalArgumentException("augment target not found: " + targetPath);
199         }
200
201         //TODO: loose this assignment afterwards #2 done
202         Map<Module, ModuleContext> generatedCtx = genCtx;
203
204         GeneratedTypeBuilder targetTypeBuilder = GenHelperUtil.findChildNodeByPath(targetSchemaNode.getPath(),
205                 generatedCtx);
206         if (targetTypeBuilder == null) {
207             targetTypeBuilder = GenHelperUtil.findCaseByPath(targetSchemaNode.getPath(), generatedCtx);
208         }
209         if (targetTypeBuilder == null) {
210             throw new NullPointerException("Target type not yet generated: " + targetSchemaNode);
211         }
212
213         final String augmentNamespacePackageName =
214                 BindingGeneratorUtil.packageNameForAugmentedGeneratedType(basePackageName, targetPath);
215
216         if (!(targetSchemaNode instanceof ChoiceSchemaNode)) {
217             generatedCtx = GenHelperUtil.addRawAugmentGenTypeDefinition(module, augmentNamespacePackageName,
218                     targetTypeBuilder.toInstance(), schemaPathAugmentListEntry.getValue(), genTypeBuilders, generatedCtx,
219                     schemaContext, verboseClassComments, typeProvider);
220         } else {
221             //TODO: #3 implement augmented choice cases scenario
222         }
223         return generatedCtx;
224     }
225
226     static Map<Module, ModuleContext> usesAugmentationToGenTypes(final SchemaContext schemaContext,
227            final String augmentPackageName, final List<AugmentationSchema> schemaPathAugmentListEntry, final Module module,
228            final UsesNode usesNode, final DataNodeContainer usesNodeParent, Map<Module, ModuleContext> genCtx,
229            Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders, final boolean verboseClassComments,
230            final TypeProvider typeProvider) {
231
232         Preconditions.checkArgument(augmentPackageName != null, "Package Name cannot be NULL.");
233
234         final SchemaPath targetPath = schemaPathAugmentListEntry.get(0).getTargetPath();
235         final SchemaNode targetSchemaNode = findOriginalTargetFromGrouping(schemaContext, targetPath, usesNode);
236         if (targetSchemaNode == null) {
237             throw new IllegalArgumentException("augment target not found: " + targetPath);
238         }
239
240         GeneratedTypeBuilder targetTypeBuilder = GenHelperUtil.findChildNodeByPath(targetSchemaNode.getPath(),
241                 genCtx);
242         if (targetTypeBuilder == null) {
243             targetTypeBuilder = GenHelperUtil.findCaseByPath(targetSchemaNode.getPath(), genCtx);
244         }
245         if (targetTypeBuilder == null) {
246             throw new NullPointerException("Target type not yet generated: " + targetSchemaNode);
247         }
248
249         if (!(targetSchemaNode instanceof ChoiceSchemaNode)) {
250             String packageName = augmentPackageName;
251             if (usesNodeParent instanceof SchemaNode) {
252                 packageName = BindingGeneratorUtil.packageNameForAugmentedGeneratedType(augmentPackageName,
253                         ((SchemaNode) usesNodeParent).getPath());
254             } else if (usesNodeParent instanceof AugmentationSchema) {
255                 Type parentTypeBuilder = genCtx.get(module).getTargetToAugmentation()
256                         .get(((AugmentationSchema) usesNodeParent).getTargetPath());
257                 packageName = parentTypeBuilder.getFullyQualifiedName();
258             }
259             genCtx = GenHelperUtil.addRawAugmentGenTypeDefinition(module, packageName,
260                     targetTypeBuilder.toInstance(), schemaPathAugmentListEntry, genTypeBuilders, genCtx,
261                     schemaContext, verboseClassComments, typeProvider);
262             return genCtx;
263         } else {
264             genCtx = generateTypesFromAugmentedChoiceCases(schemaContext, module, augmentPackageName,
265                     targetTypeBuilder.toInstance(), (ChoiceSchemaNode) targetSchemaNode,
266                     schemaPathAugmentListEntry.get(0).getChildNodes(),
267                     usesNodeParent, genCtx, verboseClassComments, genTypeBuilders, typeProvider);
268             return genCtx;
269         }
270     }
271
272     //TODO: delete this method eventually when uses-augments & augmented choice cases are implemented
273     @Deprecated
274     public static Map<Module, ModuleContext> usesAugmentationToGenTypes(final SchemaContext schemaContext, final String
275             augmentPackageName, final AugmentationSchema augSchema, final Module module, final UsesNode usesNode, final DataNodeContainer
276                                                                                 usesNodeParent, Map<Module, ModuleContext> genCtx,
277                                                                         Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders,
278                                                                         final boolean verboseClassComments, final TypeProvider typeProvider) {
279
280         Preconditions.checkArgument(augmentPackageName != null, "Package Name cannot be NULL.");
281         Preconditions.checkArgument(augSchema != null, "Augmentation Schema cannot be NULL.");
282         Preconditions.checkState(augSchema.getTargetPath() != null,
283                 "Augmentation Schema does not contain Target Path (Target Path is NULL).");
284
285         final SchemaPath targetPath = augSchema.getTargetPath();
286         final SchemaNode targetSchemaNode = findOriginalTargetFromGrouping(schemaContext, targetPath, usesNode);
287         if (targetSchemaNode == null) {
288             throw new IllegalArgumentException("augment target not found: " + targetPath);
289         }
290
291         GeneratedTypeBuilder targetTypeBuilder = GenHelperUtil.findChildNodeByPath(targetSchemaNode.getPath(),
292                 genCtx);
293         if (targetTypeBuilder == null) {
294             targetTypeBuilder = GenHelperUtil.findCaseByPath(targetSchemaNode.getPath(), genCtx);
295         }
296         if (targetTypeBuilder == null) {
297             throw new NullPointerException("Target type not yet generated: " + targetSchemaNode);
298         }
299
300         if (!(targetSchemaNode instanceof ChoiceSchemaNode)) {
301             String packageName = augmentPackageName;
302             if (usesNodeParent instanceof SchemaNode) {
303                 packageName = BindingGeneratorUtil.packageNameForAugmentedGeneratedType(augmentPackageName,
304                         ((SchemaNode) usesNodeParent).getPath());
305             } else if (usesNodeParent instanceof AugmentationSchema) {
306                 Type parentTypeBuilder = genCtx.get(module).getTypeToAugmentation().inverse().get(usesNodeParent);
307                 packageName = BindingGeneratorUtil.packageNameForAugmentedGeneratedType(
308                         parentTypeBuilder.getPackageName(), (AugmentationSchema)usesNodeParent);
309             }
310             genCtx = GenHelperUtil.addRawAugmentGenTypeDefinition(module, packageName, augmentPackageName,
311                     targetTypeBuilder.toInstance(), augSchema, genTypeBuilders, genCtx, schemaContext,
312                     verboseClassComments, typeProvider);
313             return genCtx;
314         } else {
315             genCtx = generateTypesFromAugmentedChoiceCases(schemaContext, module, augmentPackageName,
316                     targetTypeBuilder.toInstance(), (ChoiceSchemaNode) targetSchemaNode, augSchema.getChildNodes(),
317                     usesNodeParent, genCtx, verboseClassComments, genTypeBuilders, typeProvider);
318             return genCtx;
319         }
320     }
321
322     /**
323      * Convenient method to find node added by uses statement.
324      * @param schemaContext
325      * @param targetPath
326      *            node path
327      * @param parentUsesNode
328      *            parent of uses node
329      * @return node from its original location in grouping
330      */
331     private static DataSchemaNode findOriginalTargetFromGrouping(final SchemaContext schemaContext, final SchemaPath targetPath,
332                                           final UsesNode parentUsesNode) {
333         SchemaNode targetGrouping = null;
334         QName current = parentUsesNode.getGroupingPath().getPathFromRoot().iterator().next();
335         Module module = schemaContext.findModuleByNamespaceAndRevision(current.getNamespace(), current.getRevision());
336         if (module == null) {
337             throw new IllegalArgumentException("Fialed to find module for grouping in: " + parentUsesNode);
338         } else {
339             for (GroupingDefinition group : module.getGroupings()) {
340                 if (group.getQName().equals(current)) {
341                     targetGrouping = group;
342                     break;
343                 }
344             }
345         }
346
347         if (targetGrouping == null) {
348             throw new IllegalArgumentException("Failed to generate code for augment in " + parentUsesNode);
349         }
350
351         final GroupingDefinition grouping = (GroupingDefinition) targetGrouping;
352         SchemaNode result = grouping;
353         for (final QName node : targetPath.getPathFromRoot()) {
354             if (result instanceof DataNodeContainer) {
355                 final QName resultNode = QName.create(result.getQName().getModule(), node.getLocalName());
356                 result = ((DataNodeContainer) result).getDataChildByName(resultNode);
357             } else if (result instanceof ChoiceSchemaNode) {
358                 result = ((ChoiceSchemaNode) result).getCaseNodeByName(node.getLocalName());
359             }
360         }
361         if (result == null) {
362             return null;
363         }
364
365         if (result instanceof DerivableSchemaNode) {
366             DerivableSchemaNode castedResult = (DerivableSchemaNode) result;
367             Optional<? extends SchemaNode> originalNode = castedResult
368                     .getOriginal();
369             if (castedResult.isAddedByUses() && originalNode.isPresent()) {
370                 result = originalNode.get();
371             }
372         }
373
374         if (result instanceof DataSchemaNode) {
375             DataSchemaNode resultDataSchemaNode = (DataSchemaNode) result;
376             if (resultDataSchemaNode.isAddedByUses()) {
377                 // The original node is required, but we have only the copy of
378                 // the original node.
379                 // Maybe this indicates a bug in Yang parser.
380                 throw new IllegalStateException(
381                         "Failed to generate code for augment in "
382                                 + parentUsesNode);
383             } else {
384                 return resultDataSchemaNode;
385             }
386         } else {
387             throw new IllegalStateException(
388                     "Target node of uses-augment statement must be DataSchemaNode. Failed to generate code for augment in "
389                             + parentUsesNode);
390         }
391     }
392
393     /**
394      * Generates list of generated types for all the cases of a choice which are
395      * added to the choice through the augment.
396      *
397      * @param schemaContext
398      * @param module
399      *            current module
400      * @param basePackageName
401      *            string contains name of package to which augment belongs. If
402      *            an augmented choice is from an other package (pcg1) than an
403      *            augmenting choice (pcg2) then case's of the augmenting choice
404      *            will belong to pcg2.
405      * @param targetType
406      *            Type which represents target choice
407      * @param targetNode
408      *            node which represents target choice
409      * @param augmentedNodes
410      *            set of choice case nodes for which is checked if are/aren't
411      *            added to choice through augmentation
412      * @return list of generated types which represents augmented cases of
413      *         choice <code>refChoiceType</code>
414      * @throws IllegalArgumentException
415      *             <ul>
416      *             <li>if <code>basePackageName</code> is null</li>
417      *             <li>if <code>targetType</code> is null</li>
418      *             <li>if <code>augmentedNodes</code> is null</li>
419      *             </ul>
420      */
421     private static Map<Module, ModuleContext> generateTypesFromAugmentedChoiceCases(final SchemaContext schemaContext, final Module module,
422                           final String basePackageName, final Type targetType, final ChoiceSchemaNode targetNode,
423                           final Iterable<DataSchemaNode> augmentedNodes, final DataNodeContainer usesNodeParent,
424                           Map<Module, ModuleContext> genCtx, final boolean verboseClassComments,
425                           Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders, final TypeProvider typeProvider) {
426         Preconditions.checkArgument(basePackageName != null, "Base Package Name cannot be NULL.");
427         Preconditions.checkArgument(targetType != null, "Referenced Choice Type cannot be NULL.");
428         Preconditions.checkArgument(augmentedNodes != null, "Set of Choice Case Nodes cannot be NULL.");
429
430         for (final DataSchemaNode caseNode : augmentedNodes) {
431             if (caseNode != null) {
432                 final String packageName = BindingGeneratorUtil.packageNameForGeneratedType(basePackageName,
433                         caseNode.getPath(), null);
434                 final GeneratedTypeBuilder caseTypeBuilder = GenHelperUtil.addDefaultInterfaceDefinition(packageName,
435                         caseNode, module, genCtx, schemaContext, verboseClassComments, genTypeBuilders, typeProvider);
436                 caseTypeBuilder.addImplementsType(targetType);
437
438                 SchemaNode parent;
439                 final SchemaPath nodeSp = targetNode.getPath();
440                 parent = SchemaContextUtil.findDataSchemaNode(schemaContext, nodeSp.getParent());
441
442                 GeneratedTypeBuilder childOfType = null;
443                 if (parent instanceof Module) {
444                     childOfType = genCtx.get(parent).getModuleNode();
445                 } else if (parent instanceof ChoiceCaseNode) {
446                     childOfType = GenHelperUtil.findCaseByPath(parent.getPath(), genCtx);
447                 } else if (parent instanceof DataSchemaNode || parent instanceof NotificationDefinition) {
448                     childOfType = GenHelperUtil.findChildNodeByPath(parent.getPath(), genCtx);
449                 } else if (parent instanceof GroupingDefinition) {
450                     childOfType = GenHelperUtil.findGroupingByPath(parent.getPath(), genCtx);
451                 }
452
453                 if (childOfType == null) {
454                     throw new IllegalArgumentException("Failed to find parent type of choice " + targetNode);
455                 }
456
457                 ChoiceCaseNode node = null;
458                 final String caseLocalName = caseNode.getQName().getLocalName();
459                 if (caseNode instanceof ChoiceCaseNode) {
460                     node = (ChoiceCaseNode) caseNode;
461                 } else if (targetNode.getCaseNodeByName(caseLocalName) == null) {
462                     final String targetNodeLocalName = targetNode.getQName().getLocalName();
463                     for (DataSchemaNode dataSchemaNode : usesNodeParent.getChildNodes()) {
464                         if (dataSchemaNode instanceof ChoiceSchemaNode && targetNodeLocalName.equals(dataSchemaNode.getQName
465                                 ().getLocalName())) {
466                             node = ((ChoiceSchemaNode) dataSchemaNode).getCaseNodeByName(caseLocalName);
467                             break;
468                         }
469                     }
470                 } else {
471                     node = targetNode.getCaseNodeByName(caseLocalName);
472                 }
473                 final Iterable<DataSchemaNode> childNodes = node.getChildNodes();
474                 if (childNodes != null) {
475                     GenHelperUtil.resolveDataSchemaNodes(module, basePackageName, caseTypeBuilder, childOfType,
476                             childNodes, genCtx, schemaContext, verboseClassComments, genTypeBuilders, typeProvider);
477                 }
478                 genCtx.get(module).addCaseType(caseNode.getPath(), caseTypeBuilder);
479                 genCtx.get(module).addChoiceToCaseMapping(targetType, caseTypeBuilder, node);
480             }
481         }
482         return genCtx;
483     }
484 }