jvpp: remove special request<>reply mappings
[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_just_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
111         # for structures that are also used as notifications, generate dedicated notification DTO
112         if util.is_notification(func["name"]):
113             description = "notification DTO"
114             dto_path = os.path.join(dto_package, camel_case_dto_name + ".java")
115             methods = generate_dto_base_methods(camel_case_dto_name, func)
116             write_dto_file(base_package, plugin_package, base_type, camel_case_dto_name, description, dto_package,
117                            dto_path, fields, func, inputfile, methods)
118
119     flush_dump_reply_dtos(inputfile)
120
121
122 def generate_dto_base_methods(camel_case_dto_name, func):
123     methods = generate_dto_hash(func)
124     methods += generate_dto_equals(camel_case_dto_name, func)
125     methods += generate_dto_tostring(camel_case_dto_name, func)
126     return methods
127
128
129 def generate_dto_fields(camel_case_dto_name, func):
130     fields = ""
131     for t in zip(func['types'], func['args']):
132         # for retval don't generate dto field in Reply
133         field_name = util.underscore_to_camelcase(t[1])
134         if util.is_reply(camel_case_dto_name) and util.is_retval_field(field_name):
135             continue
136         fields += field_template.substitute(type=util.jni_2_java_type_mapping[t[0]],
137                                             name=field_name)
138     return fields
139
140
141 tostring_field_template = Template("""                \"$field_name=\" + $field_name + ", " +\n""")
142 tostring_array_field_template = Template("""                \"$field_name=\" + java.util.Arrays.toString($field_name) + ", " +\n""")
143 tostring_template = Template("""    @Override
144     public String toString() {
145         return "$cls_name{" +
146 $fields_tostring "}";
147     }\n\n""")
148
149
150 def generate_dto_tostring(camel_case_dto_name, func):
151     tostring_fields = ""
152     for t in zip(func['types'], func['args']):
153
154         field_name = util.underscore_to_camelcase(t[1])
155         # for retval don't generate dto field in Reply
156         if util.is_retval_field(field_name):
157             continue
158
159         # handle array types
160         if util.is_array(util.jni_2_java_type_mapping[t[0]]):
161             tostring_fields += tostring_array_field_template.substitute(field_name=field_name)
162         else:
163             tostring_fields += tostring_field_template.substitute(field_name=field_name)
164
165     return tostring_template.substitute(cls_name=camel_case_dto_name,
166                                         fields_tostring=tostring_fields[:-8])
167
168 equals_other_template = Template("""
169         final $cls_name other = ($cls_name) o;
170 \n""")
171 equals_field_template = Template("""        if (!java.util.Objects.equals(this.$field_name, other.$field_name)) {
172             return false;
173         }\n""")
174 equals_array_field_template = Template("""        if (!java.util.Arrays.equals(this.$field_name, other.$field_name)) {
175             return false;
176         }\n""")
177 equals_template = Template("""    @Override
178     public boolean equals(final Object o) {
179         if (this == o) {
180             return true;
181         }
182         if (o == null || getClass() != o.getClass()) {
183             return false;
184         }
185 $comparisons
186         return true;
187     }\n\n""")
188
189
190 def generate_dto_equals(camel_case_dto_name, func):
191     equals_fields = ""
192     for t in zip(func['types'], func['args']):
193         field_name = util.underscore_to_camelcase(t[1])
194         # for retval don't generate dto field in Reply
195         if util.is_retval_field(field_name):
196             continue
197
198         # handle array types
199         if util.is_array(util.jni_2_java_type_mapping[t[0]]):
200             equals_fields += equals_array_field_template.substitute(field_name=field_name)
201         else:
202             equals_fields += equals_field_template.substitute(field_name=field_name)
203
204     if equals_fields != "":
205         equals_fields = equals_other_template.substitute(cls_name=camel_case_dto_name) + equals_fields
206
207     return equals_template.substitute(comparisons=equals_fields)
208
209
210 hash_template = Template("""    @Override
211     @io.fd.vpp.jvpp.coverity.SuppressFBWarnings("UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD")
212     public int hashCode() {
213         return java.util.Objects.hash($fields);
214     }\n\n""")
215 hash_single_array_type_template = Template("""    @Override
216     @io.fd.vpp.jvpp.coverity.SuppressFBWarnings("UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD")
217     public int hashCode() {
218         return java.util.Arrays.hashCode($fields);
219     }\n\n""")
220
221
222 def generate_dto_hash(func):
223     hash_fields = ""
224
225     # Special handling for hashCode in case just a single array field is present. Cannot use Objects.equals since the
226     # array is mistaken for a varargs parameter. Instead use Arrays.hashCode in such case.
227     if len(func['args']) == 1:
228         single_type = func['types'][0]
229         single_type_name = func['args'][0]
230         if util.is_array(util.jni_2_java_type_mapping[single_type]):
231             return hash_single_array_type_template.substitute(fields=util.underscore_to_camelcase(single_type_name))
232
233     for t in zip(func['types'], func['args']):
234         field_name = util.underscore_to_camelcase(t[1])
235         # for retval don't generate dto field in Reply
236         if util.is_retval_field(field_name):
237             continue
238
239         hash_fields += field_name + ", "
240
241     return hash_template.substitute(fields=hash_fields[:-2])
242
243
244 def write_dto_file(base_package, plugin_package, base_type, camel_case_dto_name, description, dto_package, dto_path,
245                    fields, func, inputfile, methods):
246     dto_file = open(dto_path, 'w')
247     if base_type != "":
248         dto_file.write(dto_template.substitute(inputfile=inputfile,
249                                                description=description,
250                                                docs=util.api_message_to_javadoc(func),
251                                                cls_name=camel_case_dto_name,
252                                                fields=fields,
253                                                methods=methods,
254                                                base_package=base_package,
255                                                plugin_package=plugin_package,
256                                                base_type=base_type,
257                                                dto_package=dto_package))
258     else:
259         dto_file.write(dto_template_typeless.substitute(inputfile=inputfile,
260                                                         description=description,
261                                                         docs=util.api_message_to_javadoc(func),
262                                                         cls_name=camel_case_dto_name,
263                                                         fields=fields,
264                                                         methods=methods,
265                                                         plugin_package=plugin_package,
266                                                         dto_package=dto_package))
267     dto_file.flush()
268     dto_file.close()
269
270
271 dump_dto_suffix = "ReplyDump"
272 dump_reply_artificial_dtos = {}
273
274
275 def flush_dump_reply_dtos(inputfile):
276     for dump_reply_artificial_dto in dump_reply_artificial_dtos.values():
277         dto_path = os.path.join(dump_reply_artificial_dto['dto_package'],
278                                 dump_reply_artificial_dto['cls_name'] + ".java")
279         dto_file = open(dto_path, 'w')
280         dto_file.write(dto_template.substitute(inputfile=inputfile,
281                                                description="dump reply wrapper",
282                                                docs=dump_reply_artificial_dto['docs'],
283                                                cls_name=dump_reply_artificial_dto['cls_name'],
284                                                fields=dump_reply_artificial_dto['fields'],
285                                                methods=dump_reply_artificial_dto['methods'],
286                                                plugin_package=dump_reply_artificial_dto['plugin_package'],
287                                                base_package=dump_reply_artificial_dto['base_package'],
288                                                base_type=dump_reply_artificial_dto['base_type'],
289                                                dto_package=dump_reply_artificial_dto['dto_package']))
290         dto_file.flush()
291         dto_file.close()
292
293
294 def generate_dump_reply_dto(request_dto_name, base_package, plugin_package, dto_package, camel_case_dto_name,
295                             camel_case_method_name, func):
296     base_type = "JVppReplyDump<%s.%s.%s, %s.%s.%s>" % (
297         plugin_package, dto_package, util.remove_reply_suffix(camel_case_dto_name) + "Dump",
298         plugin_package, dto_package, camel_case_dto_name)
299     fields = "    public java.util.List<%s> %s = new java.util.ArrayList<>();" % (camel_case_dto_name, camel_case_method_name)
300     cls_name = camel_case_dto_name + dump_dto_suffix
301     # using artificial type for fields, just to bypass the is_array check in base methods generators
302     # the type is not really used
303     artificial_type = 'u8'
304
305     # In case of already existing artificial reply dump DTO, just update it
306     # Used for sub-dump dtos
307     if request_dto_name in dump_reply_artificial_dtos.keys():
308         dump_reply_artificial_dtos[request_dto_name]['fields'] += '\n' + fields
309         dump_reply_artificial_dtos[request_dto_name]['field_names'].append(func['name'])
310         dump_reply_artificial_dtos[request_dto_name]['field_types'].append(artificial_type)
311         methods = '\n' + generate_dto_base_methods(dump_reply_artificial_dtos[request_dto_name]['cls_name'],
312                                             {'args': dump_reply_artificial_dtos[request_dto_name]['field_names'],
313                                              'types': dump_reply_artificial_dtos[request_dto_name]['field_types']})
314         dump_reply_artificial_dtos[request_dto_name]['methods'] = methods
315     else:
316         methods = '\n' + generate_dto_base_methods(cls_name, {'args': [func['name']],
317                                                               'types': [artificial_type]})
318         dump_reply_artificial_dtos[request_dto_name] = ({'docs': util.api_message_to_javadoc(func),
319                                                          'cls_name': cls_name,
320                                                          'fields': fields,
321                                                          'field_names': [func['name']],
322                                                          'field_types': [artificial_type],
323                                                          # strip too many newlines at the end of base method block
324                                                          'methods': methods,
325                                                          'plugin_package': plugin_package,
326                                                          'base_package': base_package,
327                                                          'base_type': base_type,
328                                                          'dto_package': dto_package})