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