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