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