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