MDSAL-301: Binding v2 - Rework javadoc generation
[mdsal.git] / binding2 / mdsal-binding2-generator-impl / src / main / java / org / opendaylight / mdsal / binding / javav2 / generator / yang / types / TypeGenHelper.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.yang.types;
10
11 import static org.opendaylight.mdsal.binding.javav2.generator.util.BindingGeneratorUtil.encodeAngleBrackets;
12 import static org.opendaylight.mdsal.binding.javav2.generator.yang.types.TypeProviderImpl.addUnitsToGenTO;
13 import static org.opendaylight.yangtools.yang.model.util.SchemaContextUtil.findParentModule;
14
15 import com.google.common.annotations.Beta;
16 import com.google.common.base.Preconditions;
17 import com.google.common.collect.ImmutableList;
18 import java.io.Serializable;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Optional;
24 import java.util.Set;
25 import java.util.TreeMap;
26 import org.apache.commons.lang3.StringEscapeUtils;
27 import org.opendaylight.mdsal.binding.javav2.generator.context.ModuleContext;
28 import org.opendaylight.mdsal.binding.javav2.generator.util.BindingGeneratorUtil;
29 import org.opendaylight.mdsal.binding.javav2.generator.util.TypeConstants;
30 import org.opendaylight.mdsal.binding.javav2.generator.util.Types;
31 import org.opendaylight.mdsal.binding.javav2.generator.util.generated.type.builder.EnumerationBuilderImpl;
32 import org.opendaylight.mdsal.binding.javav2.generator.util.generated.type.builder.GeneratedPropertyBuilderImpl;
33 import org.opendaylight.mdsal.binding.javav2.generator.util.generated.type.builder.GeneratedTOBuilderImpl;
34 import org.opendaylight.mdsal.binding.javav2.model.api.ConcreteType;
35 import org.opendaylight.mdsal.binding.javav2.model.api.Enumeration;
36 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedTransferObject;
37 import org.opendaylight.mdsal.binding.javav2.model.api.Restrictions;
38 import org.opendaylight.mdsal.binding.javav2.model.api.Type;
39 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedPropertyBuilder;
40 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedTOBuilder;
41 import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingNamespaceType;
42 import org.opendaylight.mdsal.binding.javav2.util.BindingMapping;
43 import org.opendaylight.yangtools.yang.common.Revision;
44 import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
45 import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer;
46 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
47 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
49 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
50 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
51 import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
52 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
53 import org.opendaylight.yangtools.yang.model.api.Module;
54 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
55 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
56 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
57 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
58 import org.opendaylight.yangtools.yang.model.api.Status;
59 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
60 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
61 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
62 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
63 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
64
65 /**
66  * Auxiliary util class for {@link TypeProviderImpl} class
67  */
68 @Beta
69 final class TypeGenHelper {
70
71     private TypeGenHelper() {
72         throw new UnsupportedOperationException("Util class");
73     }
74
75     /**
76      * Gets base type definition for <code>extendTypeDef</code>. The method is
77      * recursively called until non <code>ExtendedType</code> type is found.
78      *
79      * @param extendTypeDef
80      *            type definition for which is the base type definition sought
81      * @return type definition which is base type for <code>extendTypeDef</code>
82      * @throws IllegalArgumentException
83      *             if <code>extendTypeDef</code> equal null
84      */
85     static TypeDefinition<?> baseTypeDefForExtendedType(final TypeDefinition<?> extendTypeDef) {
86         Preconditions.checkArgument(extendTypeDef != null, "Type Definition reference cannot be NULL!");
87
88         TypeDefinition<?> ret = extendTypeDef;
89         while (ret.getBaseType() != null) {
90             ret = ret.getBaseType();
91         }
92
93         return ret;
94     }
95
96     /**
97      * Creates generated TO with data about inner extended type
98      * <code>innerExtendedType</code>, about the package name
99      * <code>typedefName</code> and about the generated TO name
100      * <code>typedefName</code>.
101      *
102      * It is supposed that <code>innerExtendedType</code> is already present in
103      * {@link TypeProviderImpl#genTypeDefsContextMap genTypeDefsContextMap} to
104      * be possible set it as extended type for the returning generated TO.
105      *
106      * @param typedef
107      *            Type Definition
108      * @param innerExtendedType
109      *            extended type which is part of some other extended type
110      * @param basePackageName
111      *            string with the package name of the module
112      * @param moduleName
113      *            Module Name
114      * @return generated TO which extends generated TO for
115      *         <code>innerExtendedType</code>
116      * @throws IllegalArgumentException
117      *             <ul>
118      *             <li>if <code>extendedType</code> equals null</li>
119      *             <li>if <code>basePackageName</code> equals null</li>
120      *             <li>if <code>typedefName</code> equals null</li>
121      *             </ul>
122      */
123     @SuppressWarnings({ "rawtypes", "unchecked" })
124     static GeneratedTransferObject provideGeneratedTOFromExtendedType(final TypeDefinition<?> typedef, final
125             TypeDefinition<?> innerExtendedType, final String basePackageName, final String moduleName, final SchemaContext
126             schemaContext, final Map<String, Map<Optional<Revision>, Map<String, Type>>> genTypeDefsContextMap,
127             final ModuleContext context) {
128
129         Preconditions.checkArgument(innerExtendedType != null, "Extended type cannot be NULL!");
130         Preconditions.checkArgument(basePackageName != null, "String with base package name cannot be NULL!");
131
132         final String typedefName = typedef.getQName().getLocalName();
133         final String innerTypeDef = innerExtendedType.getQName().getLocalName();
134         final GeneratedTOBuilderImpl genTOBuilder = new GeneratedTOBuilderImpl(basePackageName, typedefName, context);
135         final String typedefDescription = encodeAngleBrackets(typedef.getDescription().orElse(null));
136
137         genTOBuilder.setDescription(typedefDescription);
138         typedef.getReference().ifPresent(genTOBuilder::setReference);
139         genTOBuilder.setSchemaPath((List) typedef.getPath().getPathFromRoot());
140         genTOBuilder.setModuleName(moduleName);
141         genTOBuilder.setTypedef(true);
142         final Restrictions r = BindingGeneratorUtil.getRestrictions(typedef);
143         genTOBuilder.setRestrictions(r);
144         if (typedef.getStatus() == Status.DEPRECATED) {
145             genTOBuilder.addAnnotation("", "Deprecated");
146         }
147
148         if (baseTypeDefForExtendedType(innerExtendedType) instanceof UnionTypeDefinition) {
149             genTOBuilder.setIsUnion(true);
150         }
151
152         Map<Optional<Revision>, Map<String, Type>> modulesByDate = null;
153         Map<String, Type> typeMap = null;
154         final Module parentModule = findParentModule(schemaContext, innerExtendedType);
155         if (parentModule != null) {
156             modulesByDate = genTypeDefsContextMap.get(parentModule.getName());
157             typeMap = modulesByDate.get(parentModule.getRevision());
158         }
159
160         if (typeMap != null) {
161             final Type type = typeMap.get(innerTypeDef);
162             if (type instanceof GeneratedTransferObject) {
163                 genTOBuilder.setExtendsType((GeneratedTransferObject) type);
164             }
165         }
166         addUnitsToGenTO(genTOBuilder, typedef.getUnits().orElse(null));
167         makeSerializable(genTOBuilder);
168
169         return genTOBuilder.toInstance();
170     }
171
172     /**
173      * Wraps base YANG type to generated TO.
174      *
175      * @param basePackageName
176      *            string with name of package to which the module belongs
177      * @param typedef
178      *            type definition which is converted to the TO
179      * @param javaType
180      *            JAVA <code>Type</code> to which is <code>typedef</code> mapped
181      * @return generated transfer object which represent<code>javaType</code>
182      */
183     static GeneratedTransferObject wrapJavaTypeIntoTO(final String basePackageName, final TypeDefinition<?> typedef,
184            final Type javaType, final String moduleName, final ModuleContext context) {
185         Preconditions.checkNotNull(javaType, "javaType cannot be null");
186         final String propertyName = "value";
187
188         final GeneratedTOBuilder genTOBuilder = typedefToTransferObject(basePackageName, typedef, moduleName, context);
189         genTOBuilder.setRestrictions(BindingGeneratorUtil.getRestrictions(typedef));
190         final GeneratedPropertyBuilder genPropBuilder = genTOBuilder.addProperty(propertyName);
191         genPropBuilder.setReturnType(javaType);
192         genTOBuilder.addEqualsIdentity(genPropBuilder);
193         genTOBuilder.addHashIdentity(genPropBuilder);
194         genTOBuilder.addToStringProperty(genPropBuilder);
195         if (typedef.getStatus() == Status.DEPRECATED) {
196             genTOBuilder.addAnnotation("", "Deprecated");
197         }
198         if (javaType instanceof ConcreteType && "String".equals(javaType.getName()) && typedef.getBaseType() != null) {
199             final List<String> regExps = resolveRegExpressionsFromTypedef(typedef);
200             addStringRegExAsConstant(genTOBuilder, regExps);
201         }
202         addUnitsToGenTO(genTOBuilder, typedef.getUnits().orElse(null));
203         genTOBuilder.setTypedef(true);
204         makeSerializable((GeneratedTOBuilderImpl) genTOBuilder);
205         return genTOBuilder.toInstance();
206     }
207
208     /**
209      * Converts the pattern constraints from <code>typedef</code> to the list of
210      * the strings which represents these constraints.
211      *
212      * @param typedef
213      *            extended type in which are the pattern constraints sought
214      * @return list of strings which represents the constraint patterns
215      * @throws IllegalArgumentException
216      *             if <code>typedef</code> equals null
217      *
218      */
219     static List<String> resolveRegExpressionsFromTypedef(final TypeDefinition<?> typedef) {
220         Preconditions.checkArgument(typedef != null, "typedef can't be null");
221
222         final List<PatternConstraint> patternConstraints;
223         if (typedef instanceof StringTypeDefinition) {
224             patternConstraints = ((StringTypeDefinition) typedef).getPatternConstraints();
225         } else {
226             patternConstraints = ImmutableList.of();
227         }
228
229         final List<String> regExps = new ArrayList<>(patternConstraints.size());
230         for (final PatternConstraint patternConstraint : patternConstraints) {
231             final String regEx = patternConstraint.getJavaPatternString();
232             final String modifiedRegEx = StringEscapeUtils.escapeJava(regEx);
233             regExps.add(modifiedRegEx);
234         }
235
236         return regExps;
237     }
238
239     /**
240      * Finds out for each type definition how many immersion (depth) is
241      * necessary to get to the base type. Every type definition is inserted to
242      * the map which key is depth and value is list of type definitions with
243      * equal depth. In next step are lists from this map concatenated to one
244      * list in ascending order according to their depth. All type definitions
245      * are in the list behind all type definitions on which depends.
246      *
247      * @param unsortedTypeDefinitions
248      *            list of type definitions which should be sorted by depth
249      * @return list of type definitions sorted according their each other
250      *         dependencies (type definitions which are depend on other type
251      *         definitions are in list behind them).
252      */
253     static List<TypeDefinition<?>> sortTypeDefinitionAccordingDepth(
254             final Collection<TypeDefinition<?>> unsortedTypeDefinitions) {
255         final List<TypeDefinition<?>> sortedTypeDefinition = new ArrayList<>();
256
257         final Map<Integer, List<TypeDefinition<?>>> typeDefinitionsDepths = new TreeMap<>();
258         for (final TypeDefinition<?> unsortedTypeDefinition : unsortedTypeDefinitions) {
259             final int depth = getTypeDefinitionDepth(unsortedTypeDefinition);
260             final List<TypeDefinition<?>> typeDefinitionsConcreteDepth = typeDefinitionsDepths.computeIfAbsent(depth, k -> new ArrayList<>());
261             typeDefinitionsConcreteDepth.add(unsortedTypeDefinition);
262         }
263
264         // SortedMap guarantees order corresponding to keys in ascending order
265         typeDefinitionsDepths.values().forEach(sortedTypeDefinition::addAll);
266
267         return sortedTypeDefinition;
268     }
269
270     /**
271      *
272      * Adds to the <code>genTOBuilder</code> the constant which contains regular
273      * expressions from the <code>regularExpressions</code>
274      *
275      * @param genTOBuilder
276      *            generated TO builder to which are
277      *            <code>regular expressions</code> added
278      * @param regularExpressions
279      *            list of string which represent regular expressions
280      * @throws IllegalArgumentException
281      *             <ul>
282      *             <li>if <code>genTOBuilder</code> equals null</li>
283      *             <li>if <code>regularExpressions</code> equals null</li>
284      *             </ul>
285      */
286     static void addStringRegExAsConstant(final GeneratedTOBuilder genTOBuilder, final List<String> regularExpressions) {
287         if (genTOBuilder == null) {
288             throw new IllegalArgumentException("Generated transfer object builder can't be null");
289         }
290         if (regularExpressions == null) {
291             throw new IllegalArgumentException("List of regular expressions can't be null");
292         }
293         if (!regularExpressions.isEmpty()) {
294             genTOBuilder.addConstant(Types.listTypeFor(BaseYangTypes.STRING_TYPE), TypeConstants.PATTERN_CONSTANT_NAME,
295                     regularExpressions);
296         }
297     }
298
299     /**
300      * Returns how many immersion is necessary to get from the type definition
301      * to the base type.
302      *
303      * @param typeDefinition
304      *            type definition for which is depth sought.
305      * @return number of immersions which are necessary to get from the type
306      *         definition to the base type
307      */
308     private static int getTypeDefinitionDepth(final TypeDefinition<?> typeDefinition) {
309         if (typeDefinition == null) {
310             return 1;
311         }
312         final TypeDefinition<?> baseType = typeDefinition.getBaseType();
313         if (baseType == null) {
314             return 1;
315         }
316
317         int depth = 1;
318         if (baseType.getBaseType() != null) {
319             depth = depth + getTypeDefinitionDepth(baseType);
320         } else if (baseType instanceof UnionTypeDefinition) {
321             final List<TypeDefinition<?>> childTypeDefinitions = ((UnionTypeDefinition) baseType).getTypes();
322             int maxChildDepth = 0;
323             int childDepth = 1;
324             for (final TypeDefinition<?> childTypeDefinition : childTypeDefinitions) {
325                 childDepth = childDepth + getTypeDefinitionDepth(childTypeDefinition);
326                 if (childDepth > maxChildDepth) {
327                     maxChildDepth = childDepth;
328                 }
329             }
330             return maxChildDepth;
331         }
332         return depth;
333     }
334
335     static List<TypeDefinition<?>> getAllTypedefs(final Module module) {
336         final List<TypeDefinition<?>> ret = new ArrayList<>();
337
338         fillRecursively(ret, module);
339
340         final Set<NotificationDefinition> notifications = module.getNotifications();
341         for (final NotificationDefinition notificationDefinition : notifications) {
342             fillRecursively(ret, notificationDefinition);
343         }
344
345         final Set<RpcDefinition> rpcs = module.getRpcs();
346         for (final RpcDefinition rpcDefinition : rpcs) {
347             ret.addAll(rpcDefinition.getTypeDefinitions());
348             final ContainerSchemaNode input = rpcDefinition.getInput();
349             if (input != null) {
350                 fillRecursively(ret, input);
351             }
352             final ContainerSchemaNode output = rpcDefinition.getOutput();
353             if (output != null) {
354                 fillRecursively(ret, output);
355             }
356         }
357
358         final Collection<DataSchemaNode> potentials = module.getChildNodes();
359
360         for (final DataSchemaNode potential : potentials) {
361             if (potential instanceof ActionNodeContainer) {
362                 final Set<ActionDefinition> actions = ((ActionNodeContainer) potential).getActions();
363                 for (final ActionDefinition action: actions) {
364                     final ContainerSchemaNode input = action.getInput();
365                     if (input != null) {
366                         fillRecursively(ret, input);
367                     }
368                     final ContainerSchemaNode output = action.getOutput();
369                     if (output != null) {
370                         fillRecursively(ret, output);
371                     }
372                 }
373             }
374         }
375
376         return ret;
377     }
378
379     private static void fillRecursively(final List<TypeDefinition<?>> list, final DataNodeContainer container) {
380         final Collection<DataSchemaNode> childNodes = container.getChildNodes();
381         if (childNodes != null) {
382             childNodes.stream().filter(childNode -> !childNode.isAugmenting()).forEach(childNode -> {
383                 if (childNode instanceof ContainerSchemaNode) {
384                     fillRecursively(list, (ContainerSchemaNode) childNode);
385                 } else if (childNode instanceof ListSchemaNode) {
386                     fillRecursively(list, (ListSchemaNode) childNode);
387                 } else if (childNode instanceof ChoiceSchemaNode) {
388                     for (final CaseSchemaNode caseNode : ((ChoiceSchemaNode) childNode).getCases().values()) {
389                         fillRecursively(list, caseNode);
390                     }
391                 }
392             });
393         }
394
395         list.addAll(container.getTypeDefinitions());
396
397         final Set<GroupingDefinition> groupings = container.getGroupings();
398         if (groupings != null) {
399             for (final GroupingDefinition grouping : groupings) {
400                 fillRecursively(list, grouping);
401             }
402         }
403     }
404
405     /**
406      * Add {@link Serializable} to implemented interfaces of this TO. Also
407      * compute and add serialVersionUID property.
408      *
409      * @param gto
410      *            transfer object which needs to be serializable
411      */
412     static void makeSerializable(final GeneratedTOBuilderImpl gto) {
413         gto.addImplementsType(Types.typeForClass(Serializable.class));
414         final GeneratedPropertyBuilder prop = new GeneratedPropertyBuilderImpl("serialVersionUID");
415         prop.setValue(Long.toString(BindingGeneratorUtil.computeDefaultSUID(gto)));
416         gto.setSUID(prop);
417     }
418
419     /**
420      * Converts <code>enumTypeDef</code> to
421      * {@link Enumeration
422      * enumeration}.
423      *
424      * @param enumTypeDef
425      *            enumeration type definition which is converted to enumeration
426      * @param enumName
427      *            string with name which is used as the enumeration name
428      * @return enumeration type which is built with data (name, enum values)
429      *         from <code>enumTypeDef</code>
430      * @throws IllegalArgumentException
431      *             <ul>
432      *             <li>if <code>enumTypeDef</code> equals null</li>
433      *             <li>if enum values of <code>enumTypeDef</code> equal null</li>
434      *             <li>if Q name of <code>enumTypeDef</code> equal null</li>
435      *             <li>if name of <code>enumTypeDef</code> equal null</li>
436      *             </ul>
437      */
438     @SuppressWarnings({ "rawtypes", "unchecked" })
439     static Enumeration provideTypeForEnum(final EnumTypeDefinition enumTypeDef, final String enumName,
440            final SchemaNode parentNode, final SchemaContext schemaContext, final ModuleContext context) {
441         Preconditions.checkArgument(enumTypeDef != null, "EnumTypeDefinition reference cannot be NULL!");
442         Preconditions.checkArgument(enumTypeDef.getQName().getLocalName() != null,
443                 "Local Name in EnumTypeDefinition QName cannot be NULL!");
444         final Module module = findParentModule(schemaContext, parentNode);
445         final String basePackageName = BindingMapping.getRootPackageName(module);
446         final String packageName;
447
448         if (parentNode instanceof TypeDefinition) {
449             packageName = BindingGeneratorUtil.packageNameWithNamespacePrefix(
450                     BindingMapping.getRootPackageName(module),
451                     BindingNamespaceType.Typedef);
452         } else {
453             packageName = basePackageName;
454         }
455
456         final EnumerationBuilderImpl enumBuilder = new EnumerationBuilderImpl(packageName, enumName, context);
457         final String enumTypedefDescription = encodeAngleBrackets(enumTypeDef.getDescription().orElse(null));
458         enumBuilder.setDescription(enumTypedefDescription);
459         enumBuilder.setReference(enumTypeDef.getReference().orElse(null));
460         enumBuilder.setModuleName(module.getName());
461         enumBuilder.setSchemaPath((List) enumTypeDef.getPath().getPathFromRoot());
462         enumBuilder.updateEnumPairsFromEnumTypeDef(enumTypeDef);
463         return enumBuilder.toInstance(null);
464     }
465
466     /**
467      * Converts <code>typedef</code> to the generated TO builder.
468      *
469      * @param basePackageName
470      *            string with name of package to which the module belongs
471      * @param typedef
472      *            type definition from which is the generated TO builder created
473      * @return generated TO builder which contains data from
474      *         <code>typedef</code> and <code>basePackageName</code>
475      */
476     @SuppressWarnings({ "unchecked", "rawtypes" })
477     private static GeneratedTOBuilderImpl typedefToTransferObject(final String basePackageName,
478             final TypeDefinition<?> typedef, final String moduleName, final ModuleContext context) {
479         final String typeDefTOName = typedef.getQName().getLocalName();
480
481         if (basePackageName != null && typeDefTOName != null) {
482             final GeneratedTOBuilderImpl newType = new GeneratedTOBuilderImpl(basePackageName, typeDefTOName, context);
483             final String typedefDescription = encodeAngleBrackets(typedef.getDescription().orElse(null));
484
485             newType.setDescription(typedefDescription);
486             typedef.getReference().ifPresent(newType::setReference);
487             newType.setSchemaPath((List) typedef.getPath().getPathFromRoot());
488             newType.setModuleName(moduleName);
489
490             return newType;
491         }
492         return null;
493     }
494
495     static Module getParentModule(final SchemaNode node, final SchemaContext schemaContext) {
496         return schemaContext.findModule(node.getPath().getPathFromRoot().iterator().next().getModule()).orElse(null);
497     }
498 }