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