jvpp: unify notification handling
[vpp.git] / src / vpp-api / java / jvpp / gen / jvppgen / dto_gen.py
1 #!/usr/bin/env python
2 #
3 # Copyright (c) 2016 Cisco and/or its affiliates.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 import os
17 from string import Template
18
19 import util
20
21 dto_template = Template("""
22 package $plugin_package.$dto_package;
23
24 /**
25  * <p>This class represents $description.
26  * <br>It was generated by dto_gen.py based on $inputfile preparsed data:
27  * <pre>
28 $docs
29  * </pre>
30  */
31 public final class $cls_name implements $base_package.$dto_package.$base_type {
32
33 $fields
34 $methods
35 }
36 """)
37
38 dto_template_typeless = Template("""
39 package $plugin_package.$dto_package;
40
41 /**
42  * <p>This class represents $description.
43  * <br>It was generated by dto_gen.py based on $inputfile preparsed data:
44  * <pre>
45 $docs
46  * </pre>
47  */
48 public final class $cls_name {
49
50 $fields
51 $methods
52 }
53 """)
54
55 field_template = Template("""    public $type $name;\n""")
56
57 send_template = Template("""    @Override
58     public int send(final $base_package.JVpp jvpp) throws io.fd.vpp.jvpp.VppInvocationException {
59         return (($plugin_package.JVpp${plugin_name})jvpp).$method_name($args);
60     }""")
61
62
63 def generate_dtos(func_list, base_package, plugin_package, plugin_name, dto_package, inputfile):
64     """ Generates dto objects in a dedicated package """
65     print "Generating DTOs"
66
67     if not os.path.exists(dto_package):
68         os.mkdir(dto_package)
69
70     for func in func_list:
71         camel_case_dto_name = util.underscore_to_camelcase_upper(func['name'])
72         camel_case_method_name = util.underscore_to_camelcase(func['name'])
73         dto_path = os.path.join(dto_package, camel_case_dto_name + ".java")
74
75         if util.is_control_ping(camel_case_dto_name):
76             continue
77
78         fields = generate_dto_fields(camel_case_dto_name, func)
79         methods = generate_dto_base_methods(camel_case_dto_name, func)
80         base_type = ""
81
82         # Generate request/reply or dump/dumpReply even if structure can be used as notification
83         if not util.is_notification(func["name"]):
84             if util.is_reply(camel_case_dto_name):
85                 description = "reply DTO"
86                 request_dto_name = util.remove_reply_suffix(camel_case_dto_name)
87                 if util.is_details(camel_case_dto_name):
88                     # FIXME assumption that dump calls end with "Dump" suffix. Not enforced in vpe.api
89                     base_type += "JVppReply<%s.%s.%s>" % (plugin_package, dto_package, request_dto_name + "Dump")
90                     generate_dump_reply_dto(request_dto_name, base_package, plugin_package, dto_package,
91                                             camel_case_dto_name, camel_case_method_name, func)
92                 else:
93                     base_type += "JVppReply<%s.%s.%s>" % (plugin_package, dto_package, request_dto_name)
94             else:
95                 args = "" if fields is "" else "this"
96                 methods += send_template.substitute(method_name=camel_case_method_name,
97                                                     base_package=base_package,
98                                                     plugin_package=plugin_package,
99                                                     plugin_name=plugin_name,
100                                                     args=args)
101                 if util.is_dump(camel_case_dto_name):
102                     base_type += "JVppDump"
103                     description = "dump request DTO"
104                 else:
105                     base_type += "JVppRequest"
106                     description = "request DTO"
107
108             write_dto_file(base_package, plugin_package, base_type, camel_case_dto_name, description, dto_package,
109                            dto_path, fields, func, inputfile, methods)
110         else:
111             # for structures that are also used as notifications, generate dedicated notification DTO
112             description = "notification DTO"
113             dto_path = os.path.join(dto_package, camel_case_dto_name + ".java")
114             methods = generate_dto_base_methods(camel_case_dto_name, func)
115             write_dto_file(base_package, plugin_package, base_type, camel_case_dto_name, description, dto_package,
116                            dto_path, fields, func, inputfile, methods)
117
118     flush_dump_reply_dtos(inputfile)
119
120
121 def generate_dto_base_methods(camel_case_dto_name, func):
122     methods = generate_dto_hash(func)
123     methods += generate_dto_equals(camel_case_dto_name, func)
124     methods += generate_dto_tostring(camel_case_dto_name, func)
125     return methods
126
127
128 def generate_dto_fields(camel_case_dto_name, func):
129     fields = ""
130     for t in zip(func['types'], func['args']):
131         # for retval don't generate dto field in Reply
132         field_name = util.underscore_to_camelcase(t[1])
133         if util.is_reply(camel_case_dto_name) and util.is_retval_field(field_name):
134             continue
135         fields += field_template.substitute(type=util.jni_2_java_type_mapping[t[0]],
136                                             name=field_name)
137     return fields
138
139
140 tostring_field_template = Template("""                \"$field_name=\" + $field_name + ", " +\n""")
141 tostring_array_field_template = Template("""                \"$field_name=\" + java.util.Arrays.toString($field_name) + ", " +\n""")
142 tostring_template = Template("""    @Override
143     public String toString() {
144         return "$cls_name{" +
145 $fields_tostring "}";
146     }\n\n""")
147
148
149 def generate_dto_tostring(camel_case_dto_name, func):
150     tostring_fields = ""
151     for t in zip(func['types'], func['args']):
152
153         field_name = util.underscore_to_camelcase(t[1])
154         # for retval don't generate dto field in Reply
155         if util.is_retval_field(field_name):
156             continue
157
158         # handle array types
159         if util.is_array(util.jni_2_java_type_mapping[t[0]]):
160             tostring_fields += tostring_array_field_template.substitute(field_name=field_name)
161         else:
162             tostring_fields += tostring_field_template.substitute(field_name=field_name)
163
164     return tostring_template.substitute(cls_name=camel_case_dto_name,
165                                         fields_tostring=tostring_fields[:-8])
166
167 equals_other_template = Template("""
168         final $cls_name other = ($cls_name) o;
169 \n""")
170 equals_field_template = Template("""        if (!java.util.Objects.equals(this.$field_name, other.$field_name)) {
171             return false;
172         }\n""")
173 equals_array_field_template = Template("""        if (!java.util.Arrays.equals(this.$field_name, other.$field_name)) {
174             return false;
175         }\n""")
176 equals_template = Template("""    @Override
177     public boolean equals(final Object o) {
178         if (this == o) {
179             return true;
180         }
181         if (o == null || getClass() != o.getClass()) {
182             return false;
183         }
184 $comparisons
185         return true;
186     }\n\n""")
187
188
189 def generate_dto_equals(camel_case_dto_name, func):
190     equals_fields = ""
191     for t in zip(func['types'], func['args']):
192         field_name = util.underscore_to_camelcase(t[1])
193         # for retval don't generate dto field in Reply
194         if util.is_retval_field(field_name):
195             continue
196
197         # handle array types
198         if util.is_array(util.jni_2_java_type_mapping[t[0]]):
199             equals_fields += equals_array_field_template.substitute(field_name=field_name)
200         else:
201             equals_fields += equals_field_template.substitute(field_name=field_name)
202
203     if equals_fields != "":
204         equals_fields = equals_other_template.substitute(cls_name=camel_case_dto_name) + equals_fields
205
206     return equals_template.substitute(comparisons=equals_fields)
207
208
209 hash_template = Template("""    @Override
210     @io.fd.vpp.jvpp.coverity.SuppressFBWarnings("UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD")
211     public int hashCode() {
212         return java.util.Objects.hash($fields);
213     }\n\n""")
214 hash_single_array_type_template = Template("""    @Override
215     @io.fd.vpp.jvpp.coverity.SuppressFBWarnings("UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD")
216     public int hashCode() {
217         return java.util.Arrays.hashCode($fields);
218     }\n\n""")
219
220
221 def generate_dto_hash(func):
222     hash_fields = ""
223
224     # Special handling for hashCode in case just a single array field is present. Cannot use Objects.equals since the
225     # array is mistaken for a varargs parameter. Instead use Arrays.hashCode in such case.
226     if len(func['args']) == 1:
227         single_type = func['types'][0]
228         single_type_name = func['args'][0]
229         if util.is_array(util.jni_2_java_type_mapping[single_type]):
230             return hash_single_array_type_template.substitute(fields=util.underscore_to_camelcase(single_type_name))
231
232     for t in zip(func['types'], func['args']):
233         field_name = util.underscore_to_camelcase(t[1])
234         # for retval don't generate dto field in Reply
235         if util.is_retval_field(field_name):
236             continue
237
238         hash_fields += field_name + ", "
239
240     return hash_template.substitute(fields=hash_fields[:-2])
241
242
243 def write_dto_file(base_package, plugin_package, base_type, camel_case_dto_name, description, dto_package, dto_path,
244                    fields, func, inputfile, methods):
245     dto_file = open(dto_path, 'w')
246     if base_type != "":
247         dto_file.write(dto_template.substitute(inputfile=inputfile,
248                                                description=description,
249                                                docs=util.api_message_to_javadoc(func),
250                                                cls_name=camel_case_dto_name,
251                                                fields=fields,
252                                                methods=methods,
253                                                base_package=base_package,
254                                                plugin_package=plugin_package,
255                                                base_type=base_type,
256                                                dto_package=dto_package))
257     else:
258         dto_file.write(dto_template_typeless.substitute(inputfile=inputfile,
259                                                         description=description,
260                                                         docs=util.api_message_to_javadoc(func),
261                                                         cls_name=camel_case_dto_name,
262                                                         fields=fields,
263                                                         methods=methods,
264                                                         plugin_package=plugin_package,
265                                                         dto_package=dto_package))
266     dto_file.flush()
267     dto_file.close()
268
269
270 dump_dto_suffix = "ReplyDump"
271 dump_reply_artificial_dtos = {}
272
273
274 def flush_dump_reply_dtos(inputfile):
275     for dump_reply_artificial_dto in dump_reply_artificial_dtos.values():
276         dto_path = os.path.join(dump_reply_artificial_dto['dto_package'],
277                                 dump_reply_artificial_dto['cls_name'] + ".java")
278         dto_file = open(dto_path, 'w')
279         dto_file.write(dto_template.substitute(inputfile=inputfile,
280                                                description="dump reply wrapper",
281                                                docs=dump_reply_artificial_dto['docs'],
282                                                cls_name=dump_reply_artificial_dto['cls_name'],
283                                                fields=dump_reply_artificial_dto['fields'],
284                                                methods=dump_reply_artificial_dto['methods'],
285                                                plugin_package=dump_reply_artificial_dto['plugin_package'],
286                                                base_package=dump_reply_artificial_dto['base_package'],
287                                                base_type=dump_reply_artificial_dto['base_type'],
288                                                dto_package=dump_reply_artificial_dto['dto_package']))
289         dto_file.flush()
290         dto_file.close()
291
292
293 def generate_dump_reply_dto(request_dto_name, base_package, plugin_package, dto_package, camel_case_dto_name,
294                             camel_case_method_name, func):
295     base_type = "JVppReplyDump<%s.%s.%s, %s.%s.%s>" % (
296         plugin_package, dto_package, util.remove_reply_suffix(camel_case_dto_name) + "Dump",
297         plugin_package, dto_package, camel_case_dto_name)
298     fields = "    public java.util.List<%s> %s = new java.util.ArrayList<>();" % (camel_case_dto_name, camel_case_method_name)
299     cls_name = camel_case_dto_name + dump_dto_suffix
300     # using artificial type for fields, just to bypass the is_array check in base methods generators
301     # the type is not really used
302     artificial_type = 'u8'
303
304     # In case of already existing artificial reply dump DTO, just update it
305     # Used for sub-dump dtos
306     if request_dto_name in dump_reply_artificial_dtos.keys():
307         dump_reply_artificial_dtos[request_dto_name]['fields'] += '\n' + fields
308         dump_reply_artificial_dtos[request_dto_name]['field_names'].append(func['name'])
309         dump_reply_artificial_dtos[request_dto_name]['field_types'].append(artificial_type)
310         methods = '\n' + generate_dto_base_methods(dump_reply_artificial_dtos[request_dto_name]['cls_name'],
311                                             {'args': dump_reply_artificial_dtos[request_dto_name]['field_names'],
312                                              'types': dump_reply_artificial_dtos[request_dto_name]['field_types']})
313         dump_reply_artificial_dtos[request_dto_name]['methods'] = methods
314     else:
315         methods = '\n' + generate_dto_base_methods(cls_name, {'args': [func['name']],
316                                                               'types': [artificial_type]})
317         dump_reply_artificial_dtos[request_dto_name] = ({'docs': util.api_message_to_javadoc(func),
318                                                          'cls_name': cls_name,
319                                                          'fields': fields,
320                                                          'field_names': [func['name']],
321                                                          'field_types': [artificial_type],
322                                                          # strip too many newlines at the end of base method block
323                                                          'methods': methods,
324                                                          'plugin_package': plugin_package,
325                                                          'base_package': base_package,
326                                                          'base_type': base_type,
327                                                          'dto_package': dto_package})