a246fc67d3ec44ecda8989673c984955f737e59c
[mdsal.git] / binding2 / mdsal-binding2-java-api-generator / src / main / java / org / opendaylight / mdsal / binding / javav2 / java / api / generator / renderers / BaseRenderer.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 com.google.common.base.Preconditions.checkArgument;
12 import static org.opendaylight.mdsal.binding.javav2.util.BindingMapping.PATTERN_CONSTANT_NAME;
13
14 import com.google.common.base.Preconditions;
15 import com.google.common.collect.ImmutableList;
16 import com.google.common.collect.ImmutableMap;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.HashMap;
20 import java.util.LinkedList;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Optional;
24 import org.apache.commons.text.StringEscapeUtils;
25 import org.eclipse.jdt.annotation.NonNull;
26 import org.opendaylight.mdsal.binding.javav2.generator.util.Types;
27 import org.opendaylight.mdsal.binding.javav2.java.api.generator.util.TextTemplateUtil;
28 import org.opendaylight.mdsal.binding.javav2.model.api.Constant;
29 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedProperty;
30 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedTransferObject;
31 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedType;
32 import org.opendaylight.mdsal.binding.javav2.model.api.MethodSignature;
33 import org.opendaylight.mdsal.binding.javav2.model.api.ParameterizedType;
34 import org.opendaylight.mdsal.binding.javav2.model.api.Type;
35 import org.opendaylight.mdsal.binding.javav2.model.api.WildcardType;
36 import org.opendaylight.yangtools.yang.common.QName;
37 import org.opendaylight.yangtools.yang.common.Revision;
38
39 public abstract class BaseRenderer {
40     private static final String COMMA = ",";
41     private static final String DOT = ".";
42
43     private final GeneratedType type;
44     private final Map<String, String> importMap;
45     /**
46      * list of all imported names for template
47      */
48     private final Map<String, String> importedNames = new HashMap<>();
49
50     protected BaseRenderer(final GeneratedType type) {
51         this.type = Preconditions.checkNotNull(type);
52         this.importMap = new HashMap<>();
53     }
54
55     /**
56      * Implementation needs to call Scala template render() method to generate string body
57      * @return rendered body
58      */
59     protected abstract String body();
60
61     protected GeneratedType getType() {
62         return type;
63     }
64
65     protected Map<String, String> getImportedNames() {
66         return importedNames;
67     }
68
69     protected String getFromImportMap(@NonNull final String typeName) {
70         return importMap.get(typeName);
71     }
72
73     protected void putToImportMap(@NonNull final String typeName, final String typePackageName) {
74         importMap.put(typeName, typePackageName);
75     }
76
77     protected void putAllToImportMap(@NonNull final Map<String, String> imports) {
78         importMap.putAll(imports);
79     }
80
81     protected Map<String, String> getImportMap() {
82         return ImmutableMap.copyOf(importMap);
83     }
84
85     /**
86      * @param intype type to format and add to imports
87      * @return formatted type
88      */
89     protected String importedName(final Type intype) {
90         putTypeIntoImports(type, intype);
91         return getExplicitType(type, intype);
92     }
93
94     protected String importedName(final Class<?> cls) {
95         return importedName(Types.typeForClass(cls));
96     }
97
98     /**
99      * @return package definition for template
100      */
101     protected String packageDefinition() {
102         final StringBuilder sb = new StringBuilder();
103         sb.append("package ")
104                 .append(type.getPackageName())
105                 .append(";\n\n");
106         return sb.toString();
107     }
108
109     /**
110      * walks through map of imports
111      * @return string of imports for template
112      */
113     private String imports() {
114         final StringBuilder sb = new StringBuilder();
115         if (!importMap.isEmpty()) {
116             for (Map.Entry<String, String> entry : importMap.entrySet()) {
117                 if (!hasSamePackage(entry.getValue())) {
118                     sb.append("import ")
119                             .append(entry.getValue())
120                             .append('.')
121                             .append(entry.getKey())
122                             .append(";\n");
123                 }
124             }
125         }
126         return sb.toString();
127     }
128
129     /**
130      * Template method which generates method parameters with their types from <code>parameters</code>.
131      *
132      * @param parameters
133      * group of generated property instances which are transformed to the method parameters
134      * @return string with the list of the method parameters with their types in JAVA format
135      */
136     protected String asArgumentsDeclaration(final Collection<GeneratedProperty> parameters) {
137         final List<CharSequence> strings = new LinkedList<>();
138         if (parameters.iterator().hasNext()) {
139             for (GeneratedProperty parameter : parameters) {
140                 final StringBuilder sb = new StringBuilder();
141                 sb.append(importedName(parameter.getReturnType()));
142                 sb.append(' ');
143                 sb.append(TextTemplateUtil.fieldName(parameter));
144                 strings.add(sb);
145             }
146         }
147         return String.join(", ", strings);
148     }
149
150     /**
151      * Checks if package of generated type and imported type is the same
152      * @param importedTypePackageName imported types package name
153      * @return equals packages
154      */
155     protected boolean hasSamePackage(final String importedTypePackageName) {
156         return type.getPackageName().equals(importedTypePackageName);
157     }
158
159     /**
160      * Evaluates if it is necessary to add the package name for type to the map of imports for parentGenType
161      * If it is so the package name is saved to the map imports.
162      *
163      * @param parentGenType generated type for which is the map of necessary imports build
164      * @param type JAVA type for which is the necessary of the package import evaluated
165      */
166     private void putTypeIntoImports(final GeneratedType parentGenType, final Type type) {
167         checkArgument(parentGenType != null, "Parent Generated Type parameter MUST be specified and cannot be NULL!");
168         checkArgument(type != null, "Type parameter MUST be specified and cannot be NULL!");
169         checkArgument(parentGenType.getPackageName() != null,
170                 "Parent Generated Type cannot have Package Name referenced as NULL!");
171
172         final String typeName = Preconditions.checkNotNull(type.getName());
173         final String typePackageName = Preconditions.checkNotNull(type.getPackageName());
174         final String parentTypeName = Preconditions.checkNotNull(parentGenType.getName());
175         if (typeName.equals(parentTypeName) || typePackageName.startsWith("java.lang") || typePackageName.isEmpty()) {
176             return;
177         }
178         if (!importMap.containsKey(typeName)) {
179             importMap.put(typeName, typePackageName);
180         }
181         if (type instanceof ParameterizedType) {
182             final ParameterizedType paramType = (ParameterizedType) type;
183             final Type[] params = paramType.getActualTypeArguments();
184             if (params != null) {
185                 for (Type param : params) {
186                     putTypeIntoImports(parentGenType, param);
187                 }
188             }
189         }
190     }
191
192     /**
193      * Builds the string which contains either the full path to the type (package name with type) or only type name
194      * if the package is among imports.
195      *
196      * @param parentGenType generated type which contains type
197      * @param type JAVA type for which is the string with type info generated
198      * @return string with type name for type in the full format or in the short format
199      */
200     private String getExplicitType(final GeneratedType parentGenType, final Type type) {
201         checkArgument(type != null, "Type parameter MUST be specified and cannot be NULL!");
202         checkArgument(importMap != null, "Imports Map cannot be NULL!");
203
204         final String typePackageName = Preconditions.checkNotNull(type.getPackageName());
205         final String typeName = Preconditions.checkNotNull(type.getName());
206         final String importedPackageName = importMap.get(typeName);
207         final StringBuilder sb;
208         if (typePackageName.equals(importedPackageName)) {
209             sb = new StringBuilder(typeName);
210             addActualTypeParameters(sb, type, parentGenType);
211             if (sb.toString().equals("Void")) {
212                 return "void";
213             }
214         } else {
215             sb = new StringBuilder();
216             if (!typePackageName.isEmpty()) {
217                 sb.append(typePackageName).append(DOT).append(typeName);
218             } else {
219                 sb.append(type.getName());
220             }
221             if (type.equals(Types.voidType())) {
222                 return "void";
223             }
224             addActualTypeParameters(sb, type, parentGenType);
225         }
226         return sb.toString();
227     }
228
229     /**
230      * Adds actual type parameters from type to builder if type is ParametrizedType.
231      *
232      * @param sb string builder which contains type name
233      * @param type JAVA Type for which is the string with type info generated
234      * @param parentGenType generated type which contains type
235      * @return adds actual type parameters to builder
236      */
237     private StringBuilder addActualTypeParameters(final StringBuilder sb, final Type type, final GeneratedType parentGenType) {
238         if (type instanceof ParameterizedType) {
239             final ParameterizedType pType = (ParameterizedType) type;
240             final Type[] pTypes = pType.getActualTypeArguments();
241             sb.append('<');
242             sb.append(getParameters(parentGenType, pTypes));
243             sb.append('>');
244         }
245         return sb;
246     }
247
248     protected GeneratedProperty findProperty(final GeneratedTransferObject gto, final String name) {
249         for (GeneratedProperty prop : gto.getProperties()) {
250             if (name.equals(prop.getName())) {
251                 return prop;
252             }
253         }
254         final GeneratedTransferObject parent = gto.getSuperType();
255         if (parent != null) {
256             return findProperty(parent, name);
257         }
258         return null;
259     }
260
261     /**
262      * Generates the string with all actual type parameters from
263      *
264      * @param parentGenType generated type for which is the JAVA code generated
265      * @param pTypes array of Type instances = actual type parameters
266      * @return string with all actual type parameters from pTypes
267      */
268     private String getParameters(final GeneratedType parentGenType, final Type[] pTypes) {
269         if (pTypes == null || pTypes.length == 0) {
270             return "?";
271         }
272         final StringBuilder sb = new StringBuilder();
273         for (int i = 0; i < pTypes.length; i++) {
274             final Type t = pTypes[i];
275
276             String separator = COMMA;
277             if (i == pTypes.length - 1) {
278                 separator = "";
279             }
280
281
282             if (t.equals(Types.voidType())) {
283                 sb.append("java.lang.Void")
284                         .append(separator);
285                 continue;
286             } else {
287
288                 String wildcardParam = "";
289                 if (t instanceof WildcardType) {
290                     wildcardParam = "? extends ";
291                 }
292
293                 sb.append(wildcardParam).append(getExplicitType(parentGenType, t)).append(separator);
294             }
295         }
296         return sb.toString();
297     }
298
299     /**
300      * Template method which generates method parameters with their types from <code>parameters</code>.
301      * InterfaceTemplate / UnionTemaplate
302      *
303      * @param parameters list of parameter instances which are transformed to the method parameters
304      * @return string with the list of the method parameters with their types in JAVA format
305      */
306     protected String generateParameters(final List<MethodSignature.Parameter> parameters) {
307         final List<CharSequence> strings = new LinkedList<>();
308         if (!parameters.isEmpty()) {
309             for (MethodSignature.Parameter parameter : parameters) {
310                 final StringBuilder sb = new StringBuilder();
311                 sb.append(importedName(parameter.getType()));
312                 sb.append(' ');
313                 sb.append(parameter.getName());
314                 strings.add(sb);
315             }
316         }
317         return String.join(", ", strings);
318     }
319
320     /**
321      * Template method which generates the getter method for field
322      *
323      * @param field generated property with data about field which is generated as the getter method
324      * @return string with the getter method source code in JAVA format
325      */
326     protected String getterMethod(final GeneratedProperty field) {
327         final StringBuilder sb = new StringBuilder();
328         final String name = TextTemplateUtil.fieldName(field);
329         final String importedName = Preconditions.checkNotNull(importedName(field.getReturnType()));
330         sb.append("public ")
331                 .append(importedName)
332                 .append(' ')
333                 .append(TextTemplateUtil.getterMethodName(field))
334                 .append("() {")
335                 .append("return ")
336                 .append(name);
337         if (!(field.getReturnType() instanceof ParameterizedType)
338                 && importedName.contains("[]")) {
339             sb.append(" == null ? null : ")
340                     .append(name)
341                     .append(".clone()");
342         }
343         sb.append(";}\n");
344         return sb.toString();
345     }
346
347     /**
348      * builds template
349      * @return generated final template
350      */
351     public String generateTemplate() {
352         final StringBuilder sb = new StringBuilder();
353         /* sb body must be filled before imports method call */
354         final String templateBody = body();
355         sb.append(packageDefinition())
356                 .append(imports())
357                 .append(templateBody);
358         return sb.toString();
359     }
360 }