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