VPP-205: jvpp plugin support.
[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, "org/openvpp/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 = 'org/openvpp/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, "org/openvpp/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 u8_array_struct_setter_template = Template("""
128     if (${java_name}) {
129         jsize cnt = (*env)->GetArrayLength (env, ${java_name});
130         size_t max_size = ${field_length};
131         if (max_size != 0 && cnt > max_size) cnt = max_size;
132         (*env)->GetByteArrayRegion(env, ${java_name}, 0, cnt, (jbyte *)mp->${c_name});
133     }
134 """)
135
136 u16_array_struct_setter_template = Template("""
137     jshort * ${java_name}ArrayElements = (*env)->GetShortArrayElements(env, ${java_name}, NULL);
138     if (${java_name}) {
139         size_t _i;
140         jsize cnt = (*env)->GetArrayLength (env, ${java_name});
141         size_t max_size = ${field_length};
142         if (max_size != 0 && cnt > max_size) cnt = max_size;
143         for (_i = 0; _i < cnt; _i++) {
144             mp->${c_name}[_i] = clib_host_to_net_u16(${java_name}ArrayElements[_i]);
145         }
146     }
147     (*env)->ReleaseShortArrayElements (env, ${java_name}, ${java_name}ArrayElements, 0);
148     """)
149
150 u32_array_struct_setter_template = Template("""
151     jint * ${java_name}ArrayElements = (*env)->GetIntArrayElements(env, ${java_name}, NULL);
152     if (${java_name}) {
153         size_t _i;
154         jsize cnt = (*env)->GetArrayLength (env, ${java_name});
155         size_t max_size = ${field_length};
156         if (max_size != 0 && cnt > max_size) cnt = max_size;
157         for (_i = 0; _i < cnt; _i++) {
158             mp->${c_name}[_i] = clib_host_to_net_u32(${java_name}ArrayElements[_i]);
159         }
160     }
161     (*env)->ReleaseIntArrayElements (env, ${java_name}, ${java_name}ArrayElements, 0);
162     """)
163
164 u64_array_struct_setter_template = Template("""
165     jlong * ${java_name}ArrayElements = (*env)->GetLongArrayElements(env, ${java_name}, NULL);
166     if (${java_name}) {
167         size_t _i;
168         jsize cnt = (*env)->GetArrayLength (env, ${java_name});
169         size_t max_size = ${field_length};
170         if (max_size != 0 && cnt > max_size) cnt = max_size;
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_org_openvpp_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
268                 # check if we are processing variable length array:
269                 if t[2][1]:
270                     field_length = util.underscore_to_camelcase(t[2][0])
271
272                 java_field_name = util.underscore_to_camelcase(c_name)
273
274                 struct_setter_template = struct_setter_templates[c_type]
275
276                 struct_setters += struct_setter_template.substitute(
277                         c_name=c_name,
278                         java_name=java_field_name,
279                         field_length=field_length)
280
281         jni_impl.append(jni_impl_template.substitute(
282                 inputfile=inputfile,
283                 api_data=util.api_message_to_javadoc(f),
284                 java_name=camel_case_function_name,
285                 c_name_uppercase=f_name_uppercase,
286                 c_name=f_name,
287                 plugin_name=plugin_name,
288                 java_plugin_name=plugin_name.title(),
289                 request_class=request_class,
290                 field_identifiers=field_identifiers,
291                 struct_setters=struct_setters,
292                 args=arguments))
293
294     return "\n".join(jni_impl)
295
296
297 dto_field_id_template = Template("""
298     jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${java_name}", "${jni_signature}");""")
299
300 default_dto_field_setter_template = Template("""
301     (*env)->Set${jni_setter}(env, dto, ${java_name}FieldId, mp->${c_name});
302 """)
303
304 variable_length_array_value_template = Template("""mp->${length_var_name}""")
305 variable_length_array_template = Template("""clib_net_to_host_${length_field_type}(${value})""")
306
307 u16_dto_field_setter_template = Template("""
308     (*env)->Set${jni_setter}(env, dto, ${java_name}FieldId, clib_net_to_host_u16(mp->${c_name}));
309 """)
310
311 u32_dto_field_setter_template = Template("""
312     (*env)->Set${jni_setter}(env, dto, ${java_name}FieldId, clib_net_to_host_u32(mp->${c_name}));
313 """)
314
315 u64_dto_field_setter_template = Template("""
316     (*env)->Set${jni_setter}(env, dto, ${java_name}FieldId, clib_net_to_host_u64(mp->${c_name}));
317 """)
318
319 u8_array_dto_field_setter_template = Template("""
320     jbyteArray ${java_name} = (*env)->NewByteArray(env, ${field_length});
321     (*env)->SetByteArrayRegion(env, ${java_name}, 0, ${field_length}, (const jbyte*)mp->${c_name});
322     (*env)->SetObjectField(env, dto, ${java_name}FieldId, ${java_name});
323 """)
324
325 u16_array_dto_field_setter_template = Template("""
326     {
327         jshortArray ${java_name} = (*env)->NewShortArray(env, ${field_length});
328         jshort * ${java_name}ArrayElements = (*env)->GetShortArrayElements(env, ${java_name}, NULL);
329         unsigned int _i;
330         for (_i = 0; _i < ${field_length}; _i++) {
331             ${java_name}ArrayElements[_i] = clib_net_to_host_u16(mp->${c_name}[_i]);
332         }
333
334         (*env)->ReleaseShortArrayElements(env,  ${java_name}, ${java_name}ArrayElements, 0);
335         (*env)->SetObjectField(env, dto, ${java_name}FieldId, ${java_name});
336     }
337 """)
338
339 u32_array_dto_field_setter_template = Template("""
340     {
341         jintArray ${java_name} = (*env)->NewIntArray(env, ${field_length});
342         jint * ${java_name}ArrayElements = (*env)->GetIntArrayElements(env, ${java_name}, NULL);
343         unsigned int _i;
344         for (_i = 0; _i < ${field_length}; _i++) {
345             ${java_name}ArrayElements[_i] = clib_net_to_host_u32(mp->${c_name}[_i]);
346         }
347
348         (*env)->ReleaseIntArrayElements(env,  ${java_name}, ${java_name}ArrayElements, 0);
349         (*env)->SetObjectField(env, dto, ${java_name}FieldId, ${java_name});
350     }
351 """)
352
353 # For each u64 array we get its elements. Then we convert values to host byte order.
354 # All changes to  jlong* buffer are written to jlongArray (isCopy is set to NULL)
355 u64_array_dto_field_setter_template = Template("""
356     {
357         jlongArray ${java_name} = (*env)->NewLongArray(env, ${field_length});
358         jlong * ${java_name}ArrayElements = (*env)->GetLongArrayElements(env, ${java_name}, NULL);
359         unsigned int _i;
360         for (_i = 0; _i < ${field_length}; _i++) {
361             ${java_name}ArrayElements[_i] = clib_net_to_host_u64(mp->${c_name}[_i]);
362         }
363
364         (*env)->ReleaseLongArrayElements(env,  ${java_name}, ${java_name}ArrayElements, 0);
365         (*env)->SetObjectField(env, dto, ${java_name}FieldId, ${java_name});
366     }
367 """)
368
369 dto_field_setter_templates = {'u8': default_dto_field_setter_template,
370                       'u16': u16_dto_field_setter_template,
371                       'u32': u32_dto_field_setter_template,
372                       'i32': u32_dto_field_setter_template,
373                       'u64': u64_dto_field_setter_template,
374                       'f64': default_dto_field_setter_template, #fixme
375                       'u8[]': u8_array_dto_field_setter_template,
376                       'u16[]': u16_array_dto_field_setter_template,
377                       'u32[]': u32_array_dto_field_setter_template,
378                       'u64[]': u64_array_dto_field_setter_template
379                       }
380
381 # code fragment for checking result of the operation before sending request reply
382 callback_err_handler_template = Template("""
383     // for negative result don't send callback message but send error callback
384     if (mp->retval<0) {
385         call_on_error("${handler_name}", mp->context, mp->retval, plugin_main->callbackClass, plugin_main->callbackObject, callbackExceptionClass);
386         return;
387     }
388     if (mp->retval == VNET_API_ERROR_IN_PROGRESS) {
389         clib_warning("Result in progress");
390         return;
391     }
392 """)
393
394 msg_handler_template = Template("""
395 /**
396  * Handler for ${handler_name} message.
397  * Generated based on $inputfile preparsed data:
398 $api_data
399  */
400 static void vl_api_${handler_name}_t_handler (vl_api_${handler_name}_t * mp)
401 {
402     ${plugin_name}_main_t *plugin_main = &${plugin_name}_main;
403     JNIEnv *env = jvpp_main.jenv;
404
405     $err_handler
406
407     jmethodID constructor = (*env)->GetMethodID(env, ${class_ref_name}Class, "<init>", "()V");
408     jmethodID callbackMethod = (*env)->GetMethodID(env, plugin_main->callbackClass, "on${dto_name}", "(Lorg/openvpp/jvpp/${plugin_name}/dto/${dto_name};)V");
409
410     jobject dto = (*env)->NewObject(env, ${class_ref_name}Class, constructor);
411     $dto_setters
412
413     (*env)->CallVoidMethod(env, plugin_main->callbackObject, callbackMethod, dto);
414 }""")
415
416 def generate_msg_handlers(func_list, plugin_name, inputfile):
417     handlers = []
418     for f in func_list:
419         handler_name = f['name']
420         dto_name = util.underscore_to_camelcase_upper(handler_name)
421         ref_name = util.underscore_to_camelcase(handler_name)
422
423         if is_manually_generated(handler_name, plugin_name) or util.is_ignored(handler_name):
424             continue
425
426         if not util.is_reply(dto_name) and not util.is_notification(handler_name):
427             continue
428
429         if util.is_notification(handler_name):
430             dto_name = util.add_notification_suffix(dto_name)
431             ref_name = util.add_notification_suffix(ref_name)
432
433         dto_setters = ''
434         err_handler = ''
435         # dto setters
436         for t in zip(f['c_types'], f['types'], f['args'], f['lengths']):
437             c_type = t[0]
438             jni_type = t[1]
439             c_name = t[2]
440             field_length = t[3][0]
441
442             if jni_type.endswith('Array') and field_length == '0':
443                 raise Exception('Variable array \'%s\' defined in message \'%s\' '
444                                 'should have defined length (e.g. \'%s[%s_length]\''
445                                 % (c_name, handler_name, c_name, c_name))
446
447             # check if we are processing variable length array
448             if t[3][1]:
449                 length_var_name = t[3][0]
450                 length_field_type = f['c_types'][f['args'].index(length_var_name)]
451                 field_length = variable_length_array_value_template.substitute(length_var_name=length_var_name)
452                 if length_field_type != 'u8':  # we need net to host conversion:
453                     field_length = variable_length_array_template.substitute(
454                         length_field_type=length_field_type, value=field_length)
455
456             # for retval don't generate setters and generate retval check
457             if util.is_retval_field(c_name):
458                 err_handler = callback_err_handler_template.substitute(
459                     handler_name=handler_name
460                 )
461                 continue
462
463             java_field_name = util.underscore_to_camelcase(c_name)
464             jni_signature = util.jni_2_signature_mapping[jni_type]
465             jni_setter = util.jni_field_accessors[jni_type]
466
467             dto_setters += dto_field_id_template.substitute(
468                     java_name=java_field_name,
469                     class_ref_name=ref_name,
470                     jni_signature=jni_signature)
471
472             dto_setter_template = dto_field_setter_templates[c_type]
473
474             dto_setters += dto_setter_template.substitute(
475                     java_name=java_field_name,
476                     jni_signature=jni_signature,
477                     c_name=c_name,
478                     jni_setter=jni_setter,
479                     field_length=field_length)
480
481         handlers.append(msg_handler_template.substitute(
482             inputfile=inputfile,
483             api_data=util.api_message_to_javadoc(f),
484             handler_name=handler_name,
485             plugin_name=plugin_name,
486             dto_name=dto_name,
487             class_ref_name=ref_name,
488             dto_setters=dto_setters,
489             err_handler=err_handler))
490
491     return "\n".join(handlers)
492
493
494 handler_registration_template = Template("""_(${upercase_name}, ${name}) \\
495 """)
496
497
498 def generate_handler_registration(func_list):
499     handler_registration = ["#define foreach_api_reply_handler \\\n"]
500     for f in func_list:
501         name = f['name']
502         camelcase_name = util.underscore_to_camelcase(f['name'])
503
504         if (not util.is_reply(camelcase_name) and not util.is_notification(name)) or util.is_ignored(name) \
505                 or util.is_control_ping(camelcase_name):
506             continue
507
508         handler_registration.append(handler_registration_template.substitute(
509             name=name,
510             upercase_name=name.upper()))
511
512     return "".join(handler_registration)
513
514
515 jvpp_c_template = Template("""/**
516  * This file contains JNI bindings for jvpp Java API.
517  * It was generated by jvpp_c_gen.py based on $inputfile
518  * (python representation of api file generated by vppapigen).
519  */
520
521 // JAVA class reference cache
522 $class_cache
523
524 // JNI bindings
525 $jni_implementations
526
527 // Message handlers
528 $msg_handlers
529
530 // Registration of message handlers in vlib
531 $handler_registration
532 """)
533
534 def generate_jvpp(func_list, plugin_name, inputfile):
535     """ Generates jvpp C file """
536     print "Generating jvpp C"
537
538     class_cache = generate_class_cache(func_list, plugin_name)
539     jni_impl = generate_jni_impl(func_list, plugin_name, inputfile)
540     msg_handlers = generate_msg_handlers(func_list, plugin_name, inputfile)
541     handler_registration = generate_handler_registration(func_list)
542
543     jvpp_c_file = open("jvpp_%s_gen.h" % plugin_name, 'w')
544     jvpp_c_file.write(jvpp_c_template.substitute(
545             inputfile=inputfile,
546             class_cache=class_cache,
547             jni_implementations=jni_impl,
548             msg_handlers=msg_handlers,
549             handler_registration=handler_registration))
550     jvpp_c_file.flush()
551     jvpp_c_file.close()
552