f0d6355cc55e4c1b6761b9df134beb22991ad804
[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.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.Optional;
24 import java.util.stream.Collectors;
25 import org.opendaylight.mdsal.binding.javav2.generator.context.ModuleContext;
26 import org.opendaylight.mdsal.binding.javav2.generator.spi.TypeProvider;
27 import org.opendaylight.mdsal.binding.javav2.generator.util.BindingGeneratorUtil;
28 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedType;
29 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedTypeBuilder;
30 import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingNamespaceType;
31 import org.opendaylight.mdsal.binding.javav2.util.BindingMapping;
32 import org.opendaylight.yangtools.yang.common.QName;
33 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
37 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.DerivableSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
40 import org.opendaylight.yangtools.yang.model.api.Module;
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<AugmentationSchemaNode> 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<AugmentationSchemaNode>>> 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<AugmentationSchemaNode> augmentations = resolveAugmentations(module, schemaContext);
127         Map<Module, ModuleContext> resultCtx = genCtx;
128
129         //let's group augments by target path
130         Map<SchemaPath, List<AugmentationSchemaNode>> augmentationsGrouped =
131                 augmentations.stream().collect(Collectors.groupingBy(AugmentationSchemaNode::getTargetPath));
132
133         List<Map.Entry<SchemaPath, List<AugmentationSchemaNode>>> sortedAugmentationsGrouped =
134                 new ArrayList<>(augmentationsGrouped.entrySet());
135         sortedAugmentationsGrouped.sort(AUGMENTS_COMP);
136
137         //process child nodes of grouped augment entries
138         for (Map.Entry<SchemaPath, List<AugmentationSchemaNode>> schemaPathAugmentListEntry : sortedAugmentationsGrouped) {
139             resultCtx = augmentationToGenTypes(basePackageName, schemaPathAugmentListEntry, module, schemaContext,
140                     verboseClassComments, resultCtx, genTypeBuilders, typeProvider);
141
142             for (AugmentationSchemaNode 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     @VisibleForTesting
166     static List<AugmentationSchemaNode> resolveAugmentations(final Module module,
167             final SchemaContext schemaContext) {
168         Preconditions.checkArgument(module != null, "Module reference cannot be NULL.");
169         Preconditions.checkState(module.getAugmentations() != null, "Augmentations Set cannot be NULL.");
170
171         final List<AugmentationSchemaNode> sortedAugmentations = module.getAugmentations().stream()
172                 .filter(aug -> !module.equals(findAugmentTargetModule(schemaContext, aug)))
173                 .collect(Collectors.toList());
174         sortedAugmentations.sort(AUGMENT_COMP);
175         return sortedAugmentations;
176     }
177
178     public static Module findAugmentTargetModule(final SchemaContext schemaContext,
179             final AugmentationSchemaNode aug) {
180         Preconditions.checkNotNull(aug, "Augmentation schema can not be null.");
181         final QName first = aug.getTargetPath().getPathFromRoot().iterator().next();
182         return schemaContext.findModule(first.getModule()).orElse(null);
183     }
184
185     /**
186      * Converts <code>augSchema</code> to list of <code>Type</code> which
187      * contains generated type for augmentation. In addition there are also
188      * generated types for all containers, list and choices which are child of
189      * <code>augSchema</code> node or a generated types for cases are added if
190      * augmented node is choice.
191      *
192      * @param basePackageName
193      *            string with the name of the package to which the augmentation
194      *            belongs
195      * @param schemaPathAugmentListEntry
196      *            list of AugmentationSchema nodes grouped by target path
197      * @param module current module
198      * @param schemaContext actual schema context
199      * @param verboseClassComments verbosity switch
200      * @param genCtx generated input context
201      * @param genTypeBuilders auxiliary type builders map
202      * @param typeProvider actual type provider instance
203      * @throws IllegalArgumentException
204      *             if <code>augmentPackageName</code> equals null
205      * @throws IllegalStateException
206      *             if augment target path is null
207      * @return generated context
208      */
209     @VisibleForTesting
210     static Map<Module, ModuleContext> augmentationToGenTypes(final String basePackageName,
211             final Entry<SchemaPath, List<AugmentationSchemaNode>> schemaPathAugmentListEntry, final Module module,
212             final SchemaContext schemaContext, final boolean verboseClassComments,
213             Map<Module, ModuleContext> genCtx, final Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders,
214             final TypeProvider typeProvider) {
215         Preconditions.checkArgument(basePackageName != null, "Package Name cannot be NULL.");
216         Preconditions.checkArgument(schemaPathAugmentListEntry != null, "Augmentation List Entry cannot be NULL.");
217         final SchemaPath targetPath = schemaPathAugmentListEntry.getKey();
218         Preconditions.checkState(targetPath != null,
219                 "Augmentation List Entry does not contain Target Path (Target Path is NULL).");
220
221         final List<AugmentationSchemaNode> augmentationSchemaList = schemaPathAugmentListEntry.getValue();
222         Preconditions.checkState(!augmentationSchemaList.isEmpty(),
223                 "Augmentation List cannot be empty.");
224
225         SchemaNode targetSchemaNode = SchemaContextUtil.findDataSchemaNode(schemaContext, targetPath);
226         if (targetSchemaNode == null) {
227             throw new IllegalArgumentException("augment target not found: " + targetPath);
228         }
229
230         GeneratedTypeBuilder targetTypeBuilder = GenHelperUtil.findChildNodeByPath(targetSchemaNode.getPath(),
231                 genCtx);
232         if (targetTypeBuilder == null) {
233             targetTypeBuilder = GenHelperUtil.findCaseByPath(targetSchemaNode.getPath(), genCtx);
234         }
235         if (targetTypeBuilder == null) {
236             throw new NullPointerException("Target type not yet generated: " + targetSchemaNode);
237         }
238
239         final String augmentPackageName =
240             BindingGeneratorUtil.packageNameWithNamespacePrefix(basePackageName, BindingNamespaceType.Data);
241
242         if (!(targetSchemaNode instanceof ChoiceSchemaNode)) {
243             genCtx = GenHelperUtil.addRawAugmentGenTypeDefinition(module, augmentPackageName,
244                 targetTypeBuilder.toInstance(), targetSchemaNode, schemaPathAugmentListEntry.getValue(),
245                 genTypeBuilders, genCtx, schemaContext, verboseClassComments, typeProvider, BindingNamespaceType.Data);
246         } else {
247             genCtx = generateTypesFromAugmentedChoiceCases(schemaContext, module, basePackageName,
248                 targetTypeBuilder.toInstance(), (ChoiceSchemaNode) targetSchemaNode,
249                 schemaPathAugmentListEntry.getValue(),genCtx, verboseClassComments, genTypeBuilders, typeProvider,
250                 BindingNamespaceType.Data);
251         }
252         return genCtx;
253     }
254
255     /**
256      * Convenient method to find node added by uses statement.
257      * @param schemaContext
258      * @param targetPath
259      *            node path
260      * @param parentUsesNode
261      *            parent of uses node
262      * @return node from its original location in grouping
263      */
264     @VisibleForTesting
265     static DataSchemaNode findOriginalTargetFromGrouping(final SchemaContext schemaContext,
266             final SchemaPath targetPath, final UsesNode parentUsesNode) {
267         SchemaNode targetGrouping = null;
268         QName current = parentUsesNode.getGroupingPath().getPathFromRoot().iterator().next();
269         Module module = schemaContext.findModule(current.getModule()).orElse(null);
270         if (module == null) {
271             throw new IllegalArgumentException("Fialed to find module for grouping in: " + parentUsesNode);
272         }
273         for (GroupingDefinition group : module.getGroupings()) {
274             if (group.getQName().equals(current)) {
275                 targetGrouping = group;
276                 break;
277             }
278         }
279
280         if (targetGrouping == null) {
281             throw new IllegalArgumentException("Failed to generate code for augment in " + parentUsesNode);
282         }
283
284         SchemaNode result = targetGrouping;
285         for (final QName node : targetPath.getPathFromRoot()) {
286             if (result instanceof DataNodeContainer) {
287                 final QName resultNode = QName.create(result.getQName().getModule(), node.getLocalName());
288                 result = ((DataNodeContainer) result).getDataChildByName(resultNode);
289             } else if (result instanceof ChoiceSchemaNode) {
290                 result = findNamedCase((ChoiceSchemaNode) result, node.getLocalName());
291             }
292         }
293         if (result == null) {
294             return null;
295         }
296
297         if (result instanceof DerivableSchemaNode) {
298             DerivableSchemaNode castedResult = (DerivableSchemaNode) result;
299             Optional<? extends SchemaNode> originalNode = castedResult
300                     .getOriginal();
301             if (castedResult.isAddedByUses() && originalNode.isPresent()) {
302                 result = originalNode.get();
303             }
304         }
305
306         if (result instanceof DataSchemaNode) {
307             DataSchemaNode resultDataSchemaNode = (DataSchemaNode) result;
308             if (resultDataSchemaNode.isAddedByUses()) {
309                 // The original node is required, but we have only the copy of
310                 // the original node.
311                 // Maybe this indicates a bug in Yang parser.
312                 throw new IllegalStateException(
313                         "Failed to generate code for augment in "
314                                 + parentUsesNode);
315             } else {
316                 return resultDataSchemaNode;
317             }
318         } else {
319             throw new IllegalStateException(
320                     "Target node of uses-augment statement must be DataSchemaNode. Failed to generate code for augment in "
321                             + parentUsesNode);
322         }
323     }
324
325     /**
326      * Generates list of generated types for all the cases of a choice which are
327      * added to the choice through the augment.
328      *
329      * @param schemaContext
330      * @param module
331      *            current module
332      * @param basePackageName
333      *            string contains name of package to which augment belongs. If
334      *            an augmented choice is from an other package (pcg1) than an
335      *            augmenting choice (pcg2) then case's of the augmenting choice
336      *            will belong to pcg2.
337      * @param targetType
338      *            Type which represents target choice
339      * @param targetNode
340      *            node which represents target choice
341      * @param schemaPathAugmentListEntry
342      *            list of AugmentationSchema nodes grouped by target path
343      * @return list of generated types which represents augmented cases of
344      *         choice <code>refChoiceType</code>
345      * @throws IllegalArgumentException
346      *             <ul>
347      *             <li>if <code>basePackageName</code> is null</li>
348      *             <li>if <code>targetType</code> is null</li>
349      *             <li>if <code>augmentedNodes</code> is null</li>
350      *             </ul>
351      */
352     @VisibleForTesting
353     static Map<Module, ModuleContext> generateTypesFromAugmentedChoiceCases(
354             final SchemaContext schemaContext, final Module module,
355             final String basePackageName, final GeneratedType targetType, final ChoiceSchemaNode targetNode,
356             final List<AugmentationSchemaNode> schemaPathAugmentListEntry,
357             final Map<Module, ModuleContext> genCtx, final boolean verboseClassComments,
358             final Map<String, Map<String, GeneratedTypeBuilder>> genTypeBuilders, final TypeProvider typeProvider,
359             final BindingNamespaceType namespaceType) {
360         Preconditions.checkArgument(basePackageName != null, "Base Package Name cannot be NULL.");
361         Preconditions.checkArgument(targetType != null, "Referenced Choice Type cannot be NULL.");
362         Preconditions.checkArgument(schemaPathAugmentListEntry != null, "Set of Choice Case Nodes cannot be NULL.");
363
364
365         for (final AugmentationSchemaNode augmentationSchema : schemaPathAugmentListEntry) {
366             for (final DataSchemaNode childNode : augmentationSchema.getChildNodes()) {
367                 if (childNode != null) {
368                     final GeneratedTypeBuilder caseTypeBuilder =
369                         GenHelperUtil.addDefaultInterfaceDefinition(basePackageName, childNode, null, module,
370                             genCtx, schemaContext, verboseClassComments, genTypeBuilders, typeProvider, namespaceType);
371                     caseTypeBuilder.addImplementsType(targetType);
372                     caseTypeBuilder.setParentTypeForBuilder(targetType.getParentTypeForBuilder());
373
374                     //Since uses augment nodes has been processed as inline nodes,
375                     //we just take two situations below.
376                     final CaseSchemaNode caseNode;
377                     if (childNode instanceof CaseSchemaNode) {
378                         caseNode = (CaseSchemaNode) childNode;
379                     } else {
380                         caseNode = findNamedCase(targetNode, childNode.getQName().getLocalName());
381                         if (caseNode == null) {
382                             throw new IllegalArgumentException("Failed to find case node " + childNode);
383                         }
384                     }
385
386                     final Collection<DataSchemaNode> childNodes = caseNode.getChildNodes();
387                     if (!childNodes.isEmpty()) {
388                         GenHelperUtil.resolveDataSchemaNodes(module, basePackageName, caseTypeBuilder,
389                                 (GeneratedTypeBuilder) targetType.getParentTypeForBuilder(),  childNodes, genCtx,
390                             schemaContext, verboseClassComments, genTypeBuilders, typeProvider, namespaceType);
391                         processUsesImplements(caseNode, module, schemaContext, genCtx, namespaceType);
392                     }
393                     genCtx.get(module).addCaseType(childNode.getPath(), caseTypeBuilder);
394                     genCtx.get(module).addChoiceToCaseMapping(targetType, caseTypeBuilder, caseNode);
395                 }
396             }
397         }
398         return genCtx;
399     }
400
401     private static CaseSchemaNode findNamedCase(final ChoiceSchemaNode choice, final String caseName) {
402         final List<CaseSchemaNode> cases = choice.findCaseNodes(caseName);
403         return cases.isEmpty() ? null : cases.get(0);
404     }
405 }