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