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