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