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