Improve JNI code generation (array truncation)
[vpp.git] / vpp-api / java / jvpp / gen / jvppgen / jvpp_c_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 # l
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
17 import os, util
18 from string import Template
19
20 def is_manually_generated(f_name, plugin_name):
21     return f_name in {'control_ping_reply'}
22
23
24 class_reference_template = Template("""jclass ${ref_name}Class;
25 """)
26
27 find_class_invocation_template = Template("""
28     ${ref_name}Class = (jclass)(*env)->NewGlobalRef(env, (*env)->FindClass(env, "io/fd/vpp/jvpp/${plugin_name}/dto/${class_name}"));
29     if ((*env)->ExceptionCheck(env)) {
30         (*env)->ExceptionDescribe(env);
31         return JNI_ERR;
32     }""")
33
34 find_class_template = Template("""
35     ${ref_name}Class = (jclass)(*env)->NewGlobalRef(env, (*env)->FindClass(env, "${class_name}"));
36     if ((*env)->ExceptionCheck(env)) {
37         (*env)->ExceptionDescribe(env);
38         return JNI_ERR;
39     }""")
40
41 delete_class_invocation_template = Template("""
42     if (${ref_name}Class) {
43         (*env)->DeleteGlobalRef(env, ${ref_name}Class);
44     }""")
45
46 class_cache_template = Template("""
47 $class_references
48 static int cache_class_references(JNIEnv* env) {
49     $find_class_invocations
50     return 0;
51 }
52
53 static void delete_class_references(JNIEnv* env) {
54     $delete_class_invocations
55 }""")
56
57 def generate_class_cache(func_list, plugin_name):
58     class_references = []
59     find_class_invocations = []
60     delete_class_invocations = []
61     for f in func_list:
62         c_name = f['name']
63         class_name = util.underscore_to_camelcase_upper(c_name)
64         ref_name = util.underscore_to_camelcase(c_name)
65
66         if util.is_ignored(c_name) or util.is_control_ping(class_name):
67             continue
68
69         if util.is_reply(class_name):
70             class_references.append(class_reference_template.substitute(
71                 ref_name=ref_name))
72             find_class_invocations.append(find_class_invocation_template.substitute(
73                 plugin_name=plugin_name,
74                 ref_name=ref_name,
75                 class_name=class_name))
76             delete_class_invocations.append(delete_class_invocation_template.substitute(ref_name=ref_name))
77         elif util.is_notification(c_name):
78             class_references.append(class_reference_template.substitute(
79                 ref_name=util.add_notification_suffix(ref_name)))
80             find_class_invocations.append(find_class_invocation_template.substitute(
81                 plugin_name=plugin_name,
82                 ref_name=util.add_notification_suffix(ref_name),
83                 class_name=util.add_notification_suffix(class_name)))
84             delete_class_invocations.append(delete_class_invocation_template.substitute(
85                 ref_name=util.add_notification_suffix(ref_name)))
86
87     # add exception class to class cache
88     ref_name = 'callbackException'
89     class_name = 'io/fd/vpp/jvpp/VppCallbackException'
90     class_references.append(class_reference_template.substitute(
91             ref_name=ref_name))
92     find_class_invocations.append(find_class_template.substitute(
93             ref_name=ref_name,
94             class_name=class_name))
95     delete_class_invocations.append(delete_class_invocation_template.substitute(ref_name=ref_name))
96
97     return class_cache_template.substitute(
98         class_references="".join(class_references), find_class_invocations="".join(find_class_invocations),
99         delete_class_invocations="".join(delete_class_invocations))
100
101
102 # TODO: cache method and field identifiers to achieve better performance
103 # https://jira.fd.io/browse/HONEYCOMB-42
104 request_class_template = Template("""
105     jclass requestClass = (*env)->FindClass(env, "io/fd/vpp/jvpp/${plugin_name}/dto/${java_name_upper}");""")
106
107 request_field_identifier_template = Template("""
108     jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, requestClass, "${java_name}", "${jni_signature}");
109     ${jni_type} ${java_name} = (*env)->Get${jni_getter}(env, request, ${java_name}FieldId);
110     """)
111
112 u8_struct_setter_template = Template("""
113     mp->${c_name} = ${java_name};""")
114
115 u16_struct_setter_template = Template("""
116     mp->${c_name} = clib_host_to_net_u16(${java_name});""")
117
118 u32_struct_setter_template = Template("""
119     mp->${c_name} = clib_host_to_net_u32(${java_name});""")
120
121 i32_struct_setter_template = Template("""
122     mp->${c_name} = clib_host_to_net_i32(${java_name});!""")
123
124 u64_struct_setter_template = Template("""
125     mp->${c_name} = clib_host_to_net_u64(${java_name});""")
126
127 fixed_array_length_enforcement_template = Template("""
128         size_t max_size = ${field_length};
129         if (cnt > max_size) cnt = max_size;""")
130
131 u8_array_struct_setter_template = Template("""
132     if (${java_name}) {
133         jsize cnt = (*env)->GetArrayLength (env, ${java_name});
134         ${field_length_check}
135         (*env)->GetByteArrayRegion(env, ${java_name}, 0, cnt, (jbyte *)mp->${c_name});
136     }
137 """)
138
139 u16_array_struct_setter_template = Template("""
140     jshort * ${java_name}ArrayElements = (*env)->GetShortArrayElements(env, ${java_name}, NULL);
141     if (${java_name}) {
142         size_t _i;
143         jsize cnt = (*env)->GetArrayLength (env, ${java_name});
144         ${field_length_check}
145         for (_i = 0; _i < cnt; _i++) {
146             mp->${c_name}[_i] = clib_host_to_net_u16(${java_name}ArrayElements[_i]);
147         }
148     }
149     (*env)->ReleaseShortArrayElements (env, ${java_name}, ${java_name}ArrayElements, 0);
150     """)
151
152 u32_array_struct_setter_template = Template("""
153     jint * ${java_name}ArrayElements = (*env)->GetIntArrayElements(env, ${java_name}, NULL);
154     if (${java_name}) {
155         size_t _i;
156         jsize cnt = (*env)->GetArrayLength (env, ${java_name});
157         ${field_length_check}
158         for (_i = 0; _i < cnt; _i++) {
159             mp->${c_name}[_i] = clib_host_to_net_u32(${java_name}ArrayElements[_i]);
160         }
161     }
162     (*env)->ReleaseIntArrayElements (env, ${java_name}, ${java_name}ArrayElements, 0);
163     """)
164
165 u64_array_struct_setter_template = Template("""
166     jlong * ${java_name}ArrayElements = (*env)->GetLongArrayElements(env, ${java_name}, NULL);
167     if (${java_name}) {
168         size_t _i;
169         jsize cnt = (*env)->GetArrayLength (env, ${java_name});
170         ${field_length_check}
171         for (_i = 0; _i < cnt; _i++) {
172             mp->${c_name}[_i] = clib_host_to_net_u64(${java_name}ArrayElements[_i]);
173         }
174     }
175     (*env)->ReleaseLongArrayElements (env, ${java_name}, ${java_name}ArrayElements, 0);
176     """)
177
178 vl_api_ip4_fib_counter_t_array_struct_setter_template = Template("""
179     // vl_api_ip4_fib_counter_t_array_field_setter_template FIXME""")
180
181 vl_api_ip6_fib_counter_t_array_struct_setter_template = Template("""
182     // vl_api_ip6_fib_counter_t_array_field_setter_template FIXME""")
183
184 struct_setter_templates = {'u8': u8_struct_setter_template,
185                           'u16': u16_struct_setter_template,
186                           'u32': u32_struct_setter_template,
187                           'i32': u32_struct_setter_template,
188                           'u64': u64_struct_setter_template,
189                           'u8[]': u8_array_struct_setter_template,
190                           'u16[]': u16_array_struct_setter_template,
191                           'u32[]': u32_array_struct_setter_template,
192                           'u64[]': u64_array_struct_setter_template,
193                           'vl_api_ip4_fib_counter_t[]': vl_api_ip4_fib_counter_t_array_struct_setter_template,
194                           'vl_api_ip6_fib_counter_t[]': vl_api_ip6_fib_counter_t_array_struct_setter_template
195                   }
196
197 jni_impl_template = Template("""
198 /**
199  * JNI binding for sending ${c_name} message.
200  * Generated based on $inputfile preparsed data:
201 $api_data
202  */
203 JNIEXPORT jint JNICALL Java_io_fd_vpp_jvpp_${plugin_name}_JVpp${java_plugin_name}Impl_${java_name}0
204 (JNIEnv * env, jclass clazz$args) {
205     ${plugin_name}_main_t *plugin_main = &${plugin_name}_main;
206     vl_api_${c_name}_t * mp;
207     u32 my_context_id = vppjni_get_context_id (&jvpp_main);
208     $request_class
209     $field_identifiers
210
211     // create message:
212     mp = vl_msg_api_alloc(sizeof(*mp));
213     memset (mp, 0, sizeof (*mp));
214     mp->_vl_msg_id = ntohs (VL_API_${c_name_uppercase} + plugin_main->msg_id_base);
215     mp->client_index = plugin_main->my_client_index;
216     mp->context = clib_host_to_net_u32 (my_context_id);
217
218     $struct_setters
219     // send message:
220     vl_msg_api_send_shmem (plugin_main->vl_input_queue, (u8 *)&mp);
221     if ((*env)->ExceptionCheck(env)) {
222         return JNI_ERR;
223     }
224     return my_context_id;
225 }""")
226
227 def generate_jni_impl(func_list, plugin_name, inputfile):
228     jni_impl = []
229     for f in func_list:
230         f_name = f['name']
231         camel_case_function_name = util.underscore_to_camelcase(f_name)
232         if is_manually_generated(f_name, plugin_name) or util.is_reply(camel_case_function_name) \
233                 or util.is_ignored(f_name) or util.is_just_notification(f_name):
234             continue
235
236         arguments = ''
237         request_class = ''
238         field_identifiers = ''
239         struct_setters = ''
240         f_name_uppercase = f_name.upper()
241
242         if f['args']:
243             arguments = ', jobject request'
244             camel_case_function_name_upper = util.underscore_to_camelcase_upper(f_name)
245
246             request_class = request_class_template.substitute(
247                     java_name_upper=camel_case_function_name_upper,
248                     plugin_name=plugin_name)
249
250             # field identifiers
251             for t in zip(f['types'], f['args']):
252                 jni_type = t[0]
253                 java_field_name = util.underscore_to_camelcase(t[1])
254                 jni_signature = util.jni_2_signature_mapping[jni_type]
255                 jni_getter = util.jni_field_accessors[jni_type]
256                 field_identifiers += request_field_identifier_template.substitute(
257                         jni_type=jni_type,
258                         java_name=java_field_name,
259                         jni_signature=jni_signature,
260                         jni_getter=jni_getter)
261
262             # field setters
263             for t in zip(f['c_types'], f['args'], f['lengths']):
264                 c_type = t[0]
265                 c_name = t[1]
266                 field_length = t[2][0]
267                 field_length_check = ""
268
269                 # check if we are processing variable length array:
270                 if t[2][1]:
271                     field_length = util.underscore_to_camelcase(t[2][0])
272
273                 # enforce max length if array has fixed length or uses variable length syntax
274                 if str(t[2][0]) != "0":
275                     field_length_check = fixed_array_length_enforcement_template.substitute(field_length=field_length)
276
277                 java_field_name = util.underscore_to_camelcase(c_name)
278
279                 struct_setter_template = struct_setter_templates[c_type]
280
281                 struct_setters += struct_setter_template.substitute(
282                         c_name=c_name,
283                         java_name=java_field_name,
284                         field_length_check=field_length_check)
285
286         jni_impl.append(jni_impl_template.substitute(
287                 inputfile=inputfile,
288                 api_data=util.api_message_to_javadoc(f),
289                 java_name=camel_case_function_name,
290                 c_name_uppercase=f_name_uppercase,
291                 c_name=f_name,
292                 plugin_name=plugin_name,
293                 java_plugin_name=plugin_name.title(),
294                 request_class=request_class,
295                 field_identifiers=field_identifiers,
296                 struct_setters=struct_setters,
297                 args=arguments))
298
299     return "\n".join(jni_impl)
300
301
302 dto_field_id_template = Template("""
303     jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${java_name}", "${jni_signature}");""")
304
305 default_dto_field_setter_template = Template("""
306     (*env)->Set${jni_setter}(env, dto, ${java_name}FieldId, mp->${c_name});
307 """)
308
309 variable_length_array_value_template = Template("""mp->${length_var_name}""")
310 variable_length_array_template = Template("""clib_net_to_host_${length_field_type}(${value})""")
311
312 u16_dto_field_setter_template = Template("""
313     (*env)->Set${jni_setter}(env, dto, ${java_name}FieldId, clib_net_to_host_u16(mp->${c_name}));
314 """)
315
316 u32_dto_field_setter_template = Template("""
317     (*env)->Set${jni_setter}(env, dto, ${java_name}FieldId, clib_net_to_host_u32(mp->${c_name}));
318 """)
319
320 u64_dto_field_setter_template = Template("""
321     (*env)->Set${jni_setter}(env, dto, ${java_name}FieldId, clib_net_to_host_u64(mp->${c_name}));
322 """)
323
324 u8_array_dto_field_setter_template = Template("""
325     jbyteArray ${java_name} = (*env)->NewByteArray(env, ${field_length});
326     (*env)->SetByteArrayRegion(env, ${java_name}, 0, ${field_length}, (const jbyte*)mp->${c_name});
327     (*env)->SetObjectField(env, dto, ${java_name}FieldId, ${java_name});
328 """)
329
330 u16_array_dto_field_setter_template = Template("""
331     {
332         jshortArray ${java_name} = (*env)->NewShortArray(env, ${field_length});
333         jshort * ${java_name}ArrayElements = (*env)->GetShortArrayElements(env, ${java_name}, NULL);
334         unsigned int _i;
335         for (_i = 0; _i < ${field_length}; _i++) {
336             ${java_name}ArrayElements[_i] = clib_net_to_host_u16(mp->${c_name}[_i]);
337         }
338
339         (*env)->ReleaseShortArrayElements(env,  ${java_name}, ${java_name}ArrayElements, 0);
340         (*env)->SetObjectField(env, dto, ${java_name}FieldId, ${java_name});
341     }
342 """)
343
344 u32_array_dto_field_setter_template = Template("""
345     {
346         jintArray ${java_name} = (*env)->NewIntArray(env, ${field_length});
347         jint * ${java_name}ArrayElements = (*env)->GetIntArrayElements(env, ${java_name}, NULL);
348         unsigned int _i;
349         for (_i = 0; _i < ${field_length}; _i++) {
350             ${java_name}ArrayElements[_i] = clib_net_to_host_u32(mp->${c_name}[_i]);
351         }
352
353         (*env)->ReleaseIntArrayElements(env,  ${java_name}, ${java_name}ArrayElements, 0);
354         (*env)->SetObjectField(env, dto, ${java_name}FieldId, ${java_name});
355     }
356 """)
357
358 # For each u64 array we get its elements. Then we convert values to host byte order.
359 # All changes to  jlong* buffer are written to jlongArray (isCopy is set to NULL)
360 u64_array_dto_field_setter_template = Template("""
361     {
362         jlongArray ${java_name} = (*env)->NewLongArray(env, ${field_length});
363         jlong * ${java_name}ArrayElements = (*env)->GetLongArrayElements(env, ${java_name}, NULL);
364         unsigned int _i;
365         for (_i = 0; _i < ${field_length}; _i++) {
366             ${java_name}ArrayElements[_i] = clib_net_to_host_u64(mp->${c_name}[_i]);
367         }
368
369         (*env)->ReleaseLongArrayElements(env,  ${java_name}, ${java_name}ArrayElements, 0);
370         (*env)->SetObjectField(env, dto, ${java_name}FieldId, ${java_name});
371     }
372 """)
373
374 dto_field_setter_templates = {'u8': default_dto_field_setter_template,
375                       'u16': u16_dto_field_setter_template,
376                       'u32': u32_dto_field_setter_template,
377                       'i32': u32_dto_field_setter_template,
378                       'u64': u64_dto_field_setter_template,
379                       'f64': default_dto_field_setter_template, #fixme
380                       'u8[]': u8_array_dto_field_setter_template,
381                       'u16[]': u16_array_dto_field_setter_template,
382                       'u32[]': u32_array_dto_field_setter_template,
383                       'u64[]': u64_array_dto_field_setter_template
384                       }
385
386 # code fragment for checking result of the operation before sending request reply
387 callback_err_handler_template = Template("""
388     // for negative result don't send callback message but send error callback
389     if (mp->retval<0) {
390         call_on_error("${handler_name}", mp->context, mp->retval, plugin_main->callbackClass, plugin_main->callbackObject, callbackExceptionClass);
391         return;
392     }
393     if (mp->retval == VNET_API_ERROR_IN_PROGRESS) {
394         clib_warning("Result in progress");
395         return;
396     }
397 """)
398
399 msg_handler_template = Template("""
400 /**
401  * Handler for ${handler_name} message.
402  * Generated based on $inputfile preparsed data:
403 $api_data
404  */
405 static void vl_api_${handler_name}_t_handler (vl_api_${handler_name}_t * mp)
406 {
407     ${plugin_name}_main_t *plugin_main = &${plugin_name}_main;
408     JNIEnv *env = jvpp_main.jenv;
409
410     $err_handler
411
412     jmethodID constructor = (*env)->GetMethodID(env, ${class_ref_name}Class, "<init>", "()V");
413     jmethodID callbackMethod = (*env)->GetMethodID(env, plugin_main->callbackClass, "on${dto_name}", "(Lio/fd/vpp/jvpp/${plugin_name}/dto/${dto_name};)V");
414
415     jobject dto = (*env)->NewObject(env, ${class_ref_name}Class, constructor);
416     $dto_setters
417
418     (*env)->CallVoidMethod(env, plugin_main->callbackObject, callbackMethod, dto);
419 }""")
420
421 def generate_msg_handlers(func_list, plugin_name, inputfile):
422     handlers = []
423     for f in func_list:
424         handler_name = f['name']
425         dto_name = util.underscore_to_camelcase_upper(handler_name)
426         ref_name = util.underscore_to_camelcase(handler_name)
427
428         if is_manually_generated(handler_name, plugin_name) or util.is_ignored(handler_name):
429             continue
430
431         if not util.is_reply(dto_name) and not util.is_notification(handler_name):
432             continue
433
434         if util.is_notification(handler_name):
435             dto_name = util.add_notification_suffix(dto_name)
436             ref_name = util.add_notification_suffix(ref_name)
437
438         dto_setters = ''
439         err_handler = ''
440         # dto setters
441         for t in zip(f['c_types'], f['types'], f['args'], f['lengths']):
442             c_type = t[0]
443             jni_type = t[1]
444             c_name = t[2]
445             field_length = t[3][0]
446
447             if jni_type.endswith('Array') and field_length == '0':
448                 raise Exception('Variable array \'%s\' defined in message \'%s\' '
449                                 'should have defined length (e.g. \'%s[%s_length]\''
450                                 % (c_name, handler_name, c_name, c_name))
451
452             # check if we are processing variable length array
453             if t[3][1]:
454                 length_var_name = t[3][0]
455                 length_field_type = f['c_types'][f['args'].index(length_var_name)]
456                 field_length = variable_length_array_value_template.substitute(length_var_name=length_var_name)
457                 if length_field_type != 'u8':  # we need net to host conversion:
458                     field_length = variable_length_array_template.substitute(
459                         length_field_type=length_field_type, value=field_length)
460
461             # for retval don't generate setters and generate retval check
462             if util.is_retval_field(c_name):
463                 err_handler = callback_err_handler_template.substitute(
464                     handler_name=handler_name
465                 )
466                 continue
467
468             java_field_name = util.underscore_to_camelcase(c_name)
469             jni_signature = util.jni_2_signature_mapping[jni_type]
470             jni_setter = util.jni_field_accessors[jni_type]
471
472             dto_setters += dto_field_id_template.substitute(
473                     java_name=java_field_name,
474                     class_ref_name=ref_name,
475                     jni_signature=jni_signature)
476
477             dto_setter_template = dto_field_setter_templates[c_type]
478
479             dto_setters += dto_setter_template.substitute(
480                     java_name=java_field_name,
481                     jni_signature=jni_signature,
482                     c_name=c_name,
483                     jni_setter=jni_setter,
484                     field_length=field_length)
485
486         handlers.append(msg_handler_template.substitute(
487             inputfile=inputfile,
488             api_data=util.api_message_to_javadoc(f),
489             handler_name=handler_name,
490             plugin_name=plugin_name,
491             dto_name=dto_name,
492             class_ref_name=ref_name,
493             dto_setters=dto_setters,
494             err_handler=err_handler))
495
496     return "\n".join(handlers)
497
498
499 handler_registration_template = Template("""_(${upercase_name}, ${name}) \\
500 """)
501
502
503 def generate_handler_registration(func_list):
504     handler_registration = ["#define foreach_api_reply_handler \\\n"]
505     for f in func_list:
506         name = f['name']
507         camelcase_name = util.underscore_to_camelcase(f['name'])
508
509         if (not util.is_reply(camelcase_name) and not util.is_notification(name)) or util.is_ignored(name) \
510                 or util.is_control_ping(camelcase_name):
511             continue
512
513         handler_registration.append(handler_registration_template.substitute(
514             name=name,
515             upercase_name=name.upper()))
516
517     return "".join(handler_registration)
518
519
520 jvpp_c_template = Template("""/**
521  * This file contains JNI bindings for jvpp Java API.
522  * It was generated by jvpp_c_gen.py based on $inputfile
523  * (python representation of api file generated by vppapigen).
524  */
525
526 // JAVA class reference cache
527 $class_cache
528
529 // JNI bindings
530 $jni_implementations
531
532 // Message handlers
533 $msg_handlers
534
535 // Registration of message handlers in vlib
536 $handler_registration
537 """)
538
539 def generate_jvpp(func_list, plugin_name, inputfile):
540     """ Generates jvpp C file """
541     print "Generating jvpp C"
542
543     class_cache = generate_class_cache(func_list, plugin_name)
544     jni_impl = generate_jni_impl(func_list, plugin_name, inputfile)
545     msg_handlers = generate_msg_handlers(func_list, plugin_name, inputfile)
546     handler_registration = generate_handler_registration(func_list)
547
548     jvpp_c_file = open("jvpp_%s_gen.h" % plugin_name, 'w')
549     jvpp_c_file.write(jvpp_c_template.substitute(
550             inputfile=inputfile,
551             class_cache=class_cache,
552             jni_implementations=jni_impl,
553             msg_handlers=msg_handlers,
554             handler_registration=handler_registration))
555     jvpp_c_file.flush()
556     jvpp_c_file.close()
557