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