MDSAL-292: Binding v2 - Pattern attribute ignored for leaf-list/leaf
[mdsal.git] / binding2 / mdsal-binding2-java-api-generator / src / main / java / org / opendaylight / mdsal / binding / javav2 / java / api / generator / renderers / BuilderRenderer.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.java.api.generator.renderers;
10
11 import static org.opendaylight.mdsal.binding.javav2.java.api.generator.util.TextTemplateUtil.DOT;
12 import static org.opendaylight.mdsal.binding.javav2.java.api.generator.util.TextTemplateUtil.getPropertyList;
13 import static org.opendaylight.mdsal.binding.javav2.java.api.generator.util.TextTemplateUtil.toFirstLower;
14
15 import com.google.common.base.Preconditions;
16 import com.google.common.base.Strings;
17 import com.google.common.collect.ClassToInstanceMap;
18 import com.google.common.collect.Collections2;
19 import com.google.common.collect.ImmutableList;
20 import com.google.common.collect.ImmutableSortedSet;
21 import java.lang.reflect.Method;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.LinkedHashSet;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Objects;
32 import java.util.Set;
33 import java.util.regex.Pattern;
34 import org.opendaylight.mdsal.binding.javav2.generator.util.BindingTypes;
35 import org.opendaylight.mdsal.binding.javav2.generator.util.ReferencedTypeImpl;
36 import org.opendaylight.mdsal.binding.javav2.generator.util.Types;
37 import org.opendaylight.mdsal.binding.javav2.generator.util.generated.type.builder.GeneratedTOBuilderImpl;
38 import org.opendaylight.mdsal.binding.javav2.java.api.generator.txt.builderConstructorHelperTemplate;
39 import org.opendaylight.mdsal.binding.javav2.java.api.generator.txt.builderTemplate;
40 import org.opendaylight.mdsal.binding.javav2.java.api.generator.txt.constantsTemplate;
41 import org.opendaylight.mdsal.binding.javav2.java.api.generator.util.AlphabeticallyTypeMemberComparator;
42 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedProperty;
43 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedTransferObject;
44 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedType;
45 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedTypeForBuilder;
46 import org.opendaylight.mdsal.binding.javav2.model.api.MethodSignature;
47 import org.opendaylight.mdsal.binding.javav2.model.api.ParameterizedType;
48 import org.opendaylight.mdsal.binding.javav2.model.api.Type;
49 import org.opendaylight.mdsal.binding.javav2.spec.base.IdentifiableItem;
50 import org.opendaylight.mdsal.binding.javav2.spec.base.Instantiable;
51 import org.opendaylight.mdsal.binding.javav2.spec.base.Item;
52 import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
53 import org.opendaylight.mdsal.binding.javav2.spec.runtime.CodeHelpers;
54 import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentable;
55 import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentation;
56 import org.opendaylight.mdsal.binding.javav2.spec.structural.AugmentationHolder;
57 import org.opendaylight.yangtools.concepts.Builder;
58 import org.opendaylight.yangtools.concepts.Identifiable;
59 import org.opendaylight.yangtools.yang.common.QName;
60
61 public class BuilderRenderer extends BaseRenderer {
62
63     /**
64      * Set of class attributes (fields) which are derived from the getter methods names
65      */
66     private final Set<GeneratedProperty> properties;
67
68     /**
69      * Set of name from properties
70      */
71     private final Map<GeneratedProperty, String> importedNamesForProperties = new HashMap<>();
72
73     /**
74      * Generated property is set if among methods is found one with the name GET_AUGMENTATION_METHOD_NAME
75      */
76     private GeneratedProperty augmentField;
77
78     boolean instantiable = false;
79
80     public BuilderRenderer(final GeneratedType type) {
81         super(type);
82         this.properties = propertiesFromMethods(createMethods());
83         putToImportMap(Builder.class.getSimpleName(), Builder.class.getPackage().getName());
84         putToImportMap(type.getName(), type.getPackageName());
85     }
86
87     @Override
88     protected String packageDefinition() {
89         final StringBuilder sb = new StringBuilder();
90         sb.append("package ")
91                 .append(((GeneratedTypeForBuilder)getType()).getPackageNameForBuilder())
92                 .append(";\n\n");
93         return sb.toString();
94     }
95
96     @Override
97     protected boolean hasSamePackage(final String importedTypePackageName) {
98         return ((GeneratedTypeForBuilder)getType()).getPackageNameForBuilder()
99                 .equals(importedTypePackageName);
100     }
101
102     /**
103      * Creates set of generated property instances from getter <code>methods</code>.
104      *
105      * @param methods set of method signature instances which should be transformed to list of properties
106      * @return set of generated property instances which represents the getter <code>methods</code>
107      */
108     private Set<GeneratedProperty> propertiesFromMethods(final Collection<MethodSignature> methods) {
109         if (methods == null || methods.isEmpty()) {
110             return Collections.emptySet();
111         }
112         final Set<GeneratedProperty> result = new LinkedHashSet<>();
113         for (MethodSignature method : methods) {
114             final GeneratedProperty createdField = propertyFromGetter(method);
115             if (createdField != null) {
116                 result.add(createdField);
117                 importedNamesForProperties.put(createdField, importedName(createdField.getReturnType()));
118             }
119         }
120         return result;
121     }
122
123     /**
124      * Creates generated property instance from the getter <code>method</code> name and return type.
125      *
126      * @param method method signature from which is the method name and return type obtained
127      * @return generated property instance for the getter <code>method</code>
128      * @throws IllegalArgumentException
129      *  <li>if the <code>method</code> equals <code>null</code></li>
130      *  <li>if the name of the <code>method</code> equals <code>null</code></li>
131      *  <li>if the name of the <code>method</code> is empty</li>
132      *  <li>if the return type of the <code>method</code> equals <code>null</code></li>
133      */
134     private GeneratedProperty propertyFromGetter(final MethodSignature method) {
135         Preconditions.checkArgument(method != null, "Method cannot be NULL");
136         Preconditions.checkArgument(!Strings.isNullOrEmpty(method.getName()),
137             "Method name cannot be NULL or empty");
138         Preconditions.checkArgument(method.getReturnType() != null,
139             "Method return type reference cannot be NULL");
140         final String prefix = Types.BOOLEAN.equals(method.getReturnType()) ? "is" : "get";
141         if (method.getName().startsWith(prefix)) {
142             final String fieldName = toFirstLower(method.getName().substring(prefix.length()));
143             final GeneratedTOBuilderImpl tmpGenTO =
144                 new GeneratedTOBuilderImpl("foo", "foo", true);
145             tmpGenTO.addProperty(fieldName).setReturnType(method.getReturnType());
146             return tmpGenTO.toInstance().getProperties().get(0);
147         }
148         return null;
149     }
150
151     /**
152      * Returns set of method signature instances which contains all the methods of the <code>genType</code>
153      * and all the methods of the implemented interfaces.
154      *
155      * @returns set of method signature instances
156      */
157     private Set<MethodSignature> createMethods() {
158         final Set<MethodSignature> methods = new LinkedHashSet<>();
159         methods.addAll(getType().getMethodDefinitions());
160         collectImplementedMethods(methods, getType().getImplements());
161         final Set<MethodSignature> sortedMethods = ImmutableSortedSet.orderedBy(
162                 new AlphabeticallyTypeMemberComparator<MethodSignature>())
163                 .addAll(methods)
164                 .build();
165         return sortedMethods;
166     }
167
168     /**
169      * Adds to the <code>methods</code> set all the methods of the <code>implementedIfcs</code>
170      * and recursively their implemented interfaces.
171      *
172      * @param methods set of method signatures
173      * @param implementedIfcs list of implemented interfaces
174      */
175     private void collectImplementedMethods(final Set<MethodSignature> methods, List<Type> implementedIfcs) {
176         if (implementedIfcs != null && !implementedIfcs.isEmpty()) {
177             for (Type implementedIfc : implementedIfcs) {
178                 if ((implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))) {
179                     final GeneratedType ifc = (GeneratedType) implementedIfc;
180                     if (implementedIfc instanceof GeneratedTypeForBuilder) {
181                         methods.addAll(ifc.getMethodDefinitions());
182                     }
183                     collectImplementedMethods(methods, ifc.getImplements());
184                 } else if (Augmentable.class.getName().equals(implementedIfc.getFullyQualifiedName())) {
185                     for (Method method : Augmentable.class.getMethods()) {
186                         if ("getAugmentation".equals(method.getName())) {
187                             final String fullyQualifiedName = method.getReturnType().getName();
188                             final String aPackage = getPackage(fullyQualifiedName);
189                             final String name = getName(fullyQualifiedName);
190                             final GeneratedTOBuilderImpl generatedTOBuilder = new GeneratedTOBuilderImpl(aPackage,
191                                     name, true);
192                             final ReferencedTypeImpl referencedType = new ReferencedTypeImpl(aPackage, name,
193                                 true, null);
194                             final ReferencedTypeImpl generic = new ReferencedTypeImpl(getType().getPackageName(),
195                                     getType().getName(), true, null);
196                             final ParameterizedType parametrizedReturnType =
197                                 Types.parameterizedTypeFor(referencedType, generic);
198                             generatedTOBuilder.addMethod(method.getName()).setReturnType(parametrizedReturnType);
199                             augmentField = propertyFromGetter(generatedTOBuilder.toInstance().getMethodDefinitions()
200                                 .get(0));
201                             getImportedNames().put("map", importedName(Map.class));
202                             getImportedNames().put("hashMap", importedName(HashMap.class));
203                             getImportedNames().put("class", importedName(Class.class));
204 //                            To do This is for third party, is it needed ?
205                             getImportedNames().put("augmentationHolder", importedName(AugmentationHolder.class));
206                             getImportedNames().put("collections", importedName(Collections.class));
207                             getImportedNames().put("augmentFieldReturnType", importedName(augmentField.getReturnType()));
208                         }
209                     }
210                 } else if (Instantiable.class.getName().equals(implementedIfc.getFullyQualifiedName())) {
211                     getImportedNames().put("class", importedName(Class.class));
212                     instantiable = true;
213                 }
214             }
215         }
216     }
217
218     /**
219      * Returns the name of the package from <code>fullyQualifiedName</code>.
220      *
221      * @param fullyQualifiedName string with fully qualified type name (package + type)
222      * @return string with the package name
223      */
224     private String getPackage(final String fullyQualifiedName) {
225         final int lastDotIndex = fullyQualifiedName.lastIndexOf(DOT);
226         return (lastDotIndex == -1) ? "" : fullyQualifiedName.substring(0, lastDotIndex);
227     }
228
229     /**
230      * Returns the name of tye type from <code>fullyQualifiedName</code>
231      *
232      * @param fullyQualifiedName string with fully qualified type name (package + type)
233      * @return string with the name of the type
234      */
235     private String getName(final String fullyQualifiedName) {
236         final int lastDotIndex = fullyQualifiedName.lastIndexOf(DOT);
237         return (lastDotIndex == -1) ? fullyQualifiedName : fullyQualifiedName.substring(lastDotIndex + 1);
238     }
239
240     public static Set<Type> getAllIfcs(final Type type) {
241         final Set<Type> baseIfcs = new HashSet<>();
242         if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
243             for (Type impl : ((GeneratedType)type).getImplements()) {
244                 if (impl instanceof GeneratedType && !(((GeneratedType)impl).getMethodDefinitions().isEmpty())) {
245                     baseIfcs.add(impl);
246                 }
247                 baseIfcs.addAll(getAllIfcs(impl));
248             }
249         }
250         return baseIfcs;
251     }
252
253     /**
254      * Method is used to find out if given type implements any interface from uses.
255      *
256      * @param type type to inspect
257      * @return has implements from uses-stmt true/false
258      */
259     public static boolean hasImplementsFromUses(GeneratedType type) {
260         for (Type impl : getAllIfcs(type)) {
261             if ((impl instanceof GeneratedType) && !(((GeneratedType)impl).getMethodDefinitions().isEmpty())) {
262                 return true;
263             }
264         }
265         return false;
266     }
267
268     public static Set<String> toListOfNames(final Set<Type> types) {
269         final Set<String> names = new HashSet<>();
270         for (Type currentType : types) {
271             names.add(currentType.getFullyQualifiedName());
272         }
273         return names;
274     }
275
276     @Override
277     protected String body() {
278         final String parentTypeForBuilderName;
279         getImportedNames().put("genType", importedName(getType()));
280         getImportedNames().put("objects", importedName(Objects.class));
281         getImportedNames().put("object", importedName(Object.class));
282         getImportedNames().put("string", importedName(String.class));
283         getImportedNames().put("arrays", importedName(Arrays.class));
284         getImportedNames().put("stringBuilder", importedName(StringBuilder.class));
285         getImportedNames().put("treeNode", importedName(TreeNode.class));
286         getImportedNames().put("instantiable", importedName(Instantiable.class));
287         getImportedNames().put("item", importedName(Item.class));
288         getImportedNames().put("identifiableItem", importedName(IdentifiableItem.class));
289         getImportedNames().put("qname", importedName(QName.class));
290         getImportedNames().put("codeHelpers", importedName(CodeHelpers.class));
291         getImportedNames().put("list", importedName(List.class));
292         getImportedNames().put("immutableList", importedName(ImmutableList.class));
293         getImportedNames().put("pattern", importedName(Pattern.class));
294
295         if (getType().getParentType() != null) {
296             getImportedNames().put("parent", importedName(getType().getParentType()));
297             parentTypeForBuilderName = getType().getParentType().getFullyQualifiedName();
298         } else if (getType().getParentTypeForBuilder() != null) {
299             getImportedNames().put("parentTypeForBuilder", importedName(getType().getParentTypeForBuilder()));
300             parentTypeForBuilderName = getType().getParentTypeForBuilder().getFullyQualifiedName();
301         } else {
302             parentTypeForBuilderName = null;
303         }
304
305         boolean childTreeNode = false;
306         boolean childTreeNodeIdent = false;
307         String keyTypeName = null;
308         if (getType().getImplements().contains(BindingTypes.TREE_CHILD_NODE)) {
309             childTreeNode = true;
310             if (getType().getImplements().contains(BindingTypes.IDENTIFIABLE)) {
311                 childTreeNodeIdent = true;
312                 final ParameterizedType pType = (ParameterizedType) getType().getImplements().get(getType()
313                     .getImplements().indexOf(BindingTypes.IDENTIFIABLE));
314                 keyTypeName = pType.getActualTypeArguments()[0].getName();
315             }
316         }
317
318         getImportedNames().put("augmentation", importedName(Augmentation.class));
319         getImportedNames().put("classInstMap", importedName(ClassToInstanceMap.class));
320
321         final String constants = constantsTemplate.render(getType(), getImportedNames(), this::importedName, false).body();
322
323         // list for generate copy constructor
324         final String copyConstructorHelper = generateListForCopyConstructor();
325         List<String> getterMethods = new ArrayList<>(Collections2.transform(properties, this::getterMethod));
326
327         return builderTemplate.render(getType(), properties, getImportedNames(), importedNamesForProperties, augmentField,
328             copyConstructorHelper, getterMethods, parentTypeForBuilderName, childTreeNode, childTreeNodeIdent,
329             keyTypeName, instantiable, constants).body();
330     }
331
332     private String generateListForCopyConstructor() {
333         final List allProps = new ArrayList<>(properties);
334         final boolean isList = implementsIfc(getType(),
335             Types.parameterizedTypeFor(Types.typeForClass(Identifiable.class), getType()));
336         final Type keyType = getKey(getType());
337         if (isList && keyType != null) {
338             final List<GeneratedProperty> keyProps = ((GeneratedTransferObject) keyType).getProperties();
339             for (GeneratedProperty keyProp : keyProps) {
340                 removeProperty(allProps, keyProp.getName());
341             }
342             removeProperty(allProps, "key");
343             getImportedNames().put("keyTypeConstructor", importedName(keyType));
344             return builderConstructorHelperTemplate.render(allProps, keyProps, getImportedNames(), getPropertyList(keyProps))
345                     .body();
346         }
347         return builderConstructorHelperTemplate.render(allProps, null, getImportedNames(), null).body();
348     }
349
350     private Type getKey(final GeneratedType genType) {
351         for (MethodSignature methodSignature : genType.getMethodDefinitions()) {
352             if ("getKey".equals(methodSignature.getName())) {
353                 return methodSignature.getReturnType();
354             }
355         }
356         return null;
357     }
358
359     private boolean implementsIfc(final GeneratedType type, final Type impl) {
360         return type.getImplements().contains(impl);
361     }
362
363     private void removeProperty(final Collection<GeneratedProperty> properties, final String name) {
364         for (final GeneratedProperty property : properties) {
365             if (name.equals(property.getName())) {
366                 properties.remove(property);
367                 break;
368             }
369         }
370     }
371 }