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