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