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