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