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