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