e2f6aa4d50548ea8525696ed14510cb2d41a1e77
[vpp.git] / src / 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 import jni_gen
21
22
23 def is_manually_generated(f_name, plugin_name):
24     return f_name in {'control_ping_reply'}
25
26
27 class_reference_template = Template("""jclass ${ref_name}Class;
28 """)
29
30 find_class_invocation_template = Template("""
31     ${ref_name}Class = (jclass)(*env)->NewGlobalRef(env, (*env)->FindClass(env, "io/fd/vpp/jvpp/${plugin_name}/dto/${class_name}"));
32     if ((*env)->ExceptionCheck(env)) {
33         (*env)->ExceptionDescribe(env);
34         return JNI_ERR;
35     }""")
36
37 find_class_template = Template("""
38     ${ref_name}Class = (jclass)(*env)->NewGlobalRef(env, (*env)->FindClass(env, "${class_name}"));
39     if ((*env)->ExceptionCheck(env)) {
40         (*env)->ExceptionDescribe(env);
41         return JNI_ERR;
42     }""")
43
44 delete_class_invocation_template = Template("""
45     if (${ref_name}Class) {
46         (*env)->DeleteGlobalRef(env, ${ref_name}Class);
47     }""")
48
49 class_cache_template = Template("""
50 $class_references
51 static int cache_class_references(JNIEnv* env) {
52     $find_class_invocations
53     return 0;
54 }
55
56 static void delete_class_references(JNIEnv* env) {
57     $delete_class_invocations
58 }""")
59
60
61 def generate_class_cache(func_list, plugin_name):
62     class_references = []
63     find_class_invocations = []
64     delete_class_invocations = []
65     for f in func_list:
66         c_name = f['name']
67         class_name = util.underscore_to_camelcase_upper(c_name)
68         ref_name = util.underscore_to_camelcase(c_name)
69
70         if util.is_ignored(c_name) or util.is_control_ping(class_name):
71             continue
72
73         class_references.append(class_reference_template.substitute(
74             ref_name=ref_name))
75         find_class_invocations.append(find_class_invocation_template.substitute(
76             plugin_name=plugin_name,
77             ref_name=ref_name,
78             class_name=class_name))
79         delete_class_invocations.append(delete_class_invocation_template.substitute(ref_name=ref_name))
80
81     # add exception class to class cache
82     ref_name = 'callbackException'
83     class_name = 'io/fd/vpp/jvpp/VppCallbackException'
84     class_references.append(class_reference_template.substitute(
85             ref_name=ref_name))
86     find_class_invocations.append(find_class_template.substitute(
87             ref_name=ref_name,
88             class_name=class_name))
89     delete_class_invocations.append(delete_class_invocation_template.substitute(ref_name=ref_name))
90
91     return class_cache_template.substitute(
92         class_references="".join(class_references), find_class_invocations="".join(find_class_invocations),
93         delete_class_invocations="".join(delete_class_invocations))
94
95
96 # TODO: cache method and field identifiers to achieve better performance
97 # https://jira.fd.io/browse/HONEYCOMB-42
98 request_class_template = Template("""
99     jclass requestClass = (*env)->FindClass(env, "io/fd/vpp/jvpp/${plugin_name}/dto/${java_name_upper}");""")
100
101 request_field_identifier_template = Template("""
102     jfieldID ${field_reference_name}FieldId = (*env)->GetFieldID(env, ${object_name}Class, "${field_name}", "${jni_signature}");
103     ${jni_type} ${field_reference_name} = (*env)->Get${jni_getter}(env, ${object_name}, ${field_reference_name}FieldId);
104     """)
105
106 jni_msg_size_template = Template(""" + ${array_length}*sizeof(${element_type})""")
107
108 jni_impl_template = Template("""
109 /**
110  * JNI binding for sending ${c_name} message.
111  * Generated based on $inputfile preparsed data:
112 $api_data
113  */
114 JNIEXPORT jint JNICALL Java_io_fd_vpp_jvpp_${plugin_name}_JVpp${java_plugin_name}Impl_${field_name}0
115 (JNIEnv * env, jclass clazz$args) {
116     ${plugin_name}_main_t *plugin_main = &${plugin_name}_main;
117     vl_api_${c_name}_t * mp;
118     u32 my_context_id = vppjni_get_context_id (&jvpp_main);
119     $request_class
120
121     $jni_identifiers
122
123     // create message:
124     mp = vl_msg_api_alloc(${msg_size});
125     memset (mp, 0, ${msg_size});
126     mp->_vl_msg_id = ntohs (get_message_id(env, "${c_name}_${crc}"));
127     mp->client_index = plugin_main->my_client_index;
128     mp->context = clib_host_to_net_u32 (my_context_id);
129
130     $msg_initialization
131
132     // send message:
133     if (CLIB_DEBUG > 1)
134         clib_warning ("Sending ${field_name} event message");
135     vl_msg_api_send_shmem (plugin_main->vl_input_queue, (u8 *)&mp);
136     if ((*env)->ExceptionCheck(env)) {
137         return JNI_ERR;
138     }
139     return my_context_id;
140 }""")
141
142 def generate_jni_impl(func_list, plugin_name, inputfile):
143     jni_impl = []
144     for f in func_list:
145         f_name = f['name']
146         camel_case_function_name = util.underscore_to_camelcase(f_name)
147         if is_manually_generated(f_name, plugin_name) or util.is_reply(camel_case_function_name) \
148                 or util.is_ignored(f_name) or util.is_just_notification(f_name):
149             continue
150
151         arguments = ''
152         request_class = ''
153         jni_identifiers = ''
154         msg_initialization = ''
155         f_name_uppercase = f_name.upper()
156         msg_size = 'sizeof(*mp)'
157
158         if f['args']:
159             arguments = ', jobject request'
160             camel_case_function_name_upper = util.underscore_to_camelcase_upper(f_name)
161
162             request_class = request_class_template.substitute(
163                     java_name_upper=camel_case_function_name_upper,
164                     plugin_name=plugin_name)
165
166             for t in zip(f['types'], f['args'], f['lengths'], f['arg_types']):
167                 field_name = util.underscore_to_camelcase(t[1])
168                 is_variable_len_array = t[2][1]
169                 if is_variable_len_array:
170                     msg_size += jni_msg_size_template.substitute(array_length=util.underscore_to_camelcase(t[2][0]),
171                                                                  element_type=t[3])
172                 jni_identifiers += jni_gen.jni_request_identifiers_for_type(field_type=t[0],
173                                                                             field_reference_name=field_name,
174                                                                             field_name=field_name)
175                 msg_initialization += jni_gen.jni_request_binding_for_type(field_type=t[0], c_name=t[1],
176                                                                            field_reference_name=field_name,
177                                                                            field_length=t[2][0],
178                                                                            is_variable_len_array=is_variable_len_array)
179
180         jni_impl.append(jni_impl_template.substitute(
181                 inputfile=inputfile,
182                 api_data=util.api_message_to_javadoc(f),
183                 field_reference_name=camel_case_function_name,
184                 field_name=camel_case_function_name,
185                 c_name_uppercase=f_name_uppercase,
186                 c_name=f_name,
187                 crc=f['crc'],
188                 plugin_name=plugin_name,
189                 java_plugin_name=plugin_name.title(),
190                 request_class=request_class,
191                 jni_identifiers=jni_identifiers,
192                 msg_size=msg_size,
193                 msg_initialization=msg_initialization,
194                 args=arguments))
195
196     return "\n".join(jni_impl)
197
198 # code fragment for checking result of the operation before sending request reply
199 callback_err_handler_template = Template("""
200     // for negative result don't send callback message but send error callback
201     if (mp->retval<0) {
202         call_on_error("${handler_name}", mp->context, mp->retval, plugin_main->callbackClass, plugin_main->callbackObject, callbackExceptionClass);
203         return;
204     }
205     if (mp->retval == VNET_API_ERROR_IN_PROGRESS) {
206         clib_warning("Result in progress");
207         return;
208     }
209 """)
210
211 msg_handler_template = Template("""
212 /**
213  * Handler for ${handler_name} message.
214  * Generated based on $inputfile preparsed data:
215 $api_data
216  */
217 static void vl_api_${handler_name}_t_handler (vl_api_${handler_name}_t * mp)
218 {
219     ${plugin_name}_main_t *plugin_main = &${plugin_name}_main;
220     JNIEnv *env = jvpp_main.jenv;
221     jthrowable exc;
222     $err_handler
223
224     if (CLIB_DEBUG > 1)
225         clib_warning ("Received ${handler_name} event message");
226
227     jmethodID constructor = (*env)->GetMethodID(env, ${class_ref_name}Class, "<init>", "()V");
228
229     // User does not have to provide callbacks for all VPP messages.
230     // We are ignoring messages that are not supported by user.
231     (*env)->ExceptionClear(env); // just in case exception occurred in different place and was not properly cleared
232     jmethodID callbackMethod = (*env)->GetMethodID(env, plugin_main->callbackClass, "on${dto_name}", "(Lio/fd/vpp/jvpp/${plugin_name}/dto/${dto_name};)V");
233     exc = (*env)->ExceptionOccurred(env);
234     if (exc) {
235         clib_warning("Unable to extract on${dto_name} method reference from ${plugin_name} plugin's callbackClass. Ignoring message.\\n");
236         (*env)->ExceptionDescribe(env);
237         (*env)->ExceptionClear(env);
238         return;
239     }
240
241     jobject dto = (*env)->NewObject(env, ${class_ref_name}Class, constructor);
242     $dto_setters
243
244     (*env)->CallVoidMethod(env, plugin_main->callbackObject, callbackMethod, dto);
245     // free DTO as per http://stackoverflow.com/questions/1340938/memory-leak-when-calling-java-code-from-c-using-jni
246     (*env)->DeleteLocalRef(env, dto);
247 }""")
248
249
250 def generate_msg_handlers(func_list, plugin_name, inputfile):
251     handlers = []
252     for f in func_list:
253         handler_name = f['name']
254         dto_name = util.underscore_to_camelcase_upper(handler_name)
255         ref_name = util.underscore_to_camelcase(handler_name)
256
257         if is_manually_generated(handler_name, plugin_name) or util.is_ignored(handler_name):
258             continue
259
260         if not util.is_reply(dto_name) and not util.is_notification(handler_name):
261             continue
262
263         dto_setters = ''
264         err_handler = ''
265         # dto setters
266         for t in zip(f['types'], f['args'], f['lengths']):
267             c_name = t[1]
268             java_name = util.underscore_to_camelcase(c_name)
269             field_length = t[2][0]
270             is_variable_len_array = t[2][1]
271             length_field_type = None
272             if is_variable_len_array:
273                 length_field_type = f['types'][f['args'].index(field_length)]
274             dto_setters += jni_gen.jni_reply_handler_for_type(handler_name=handler_name, ref_name=ref_name,
275                                                               field_type=t[0], c_name=t[1],
276                                                               field_reference_name=java_name,
277                                                               field_name=java_name, field_length=field_length,
278                                                               is_variable_len_array=is_variable_len_array,
279                                                               length_field_type=length_field_type)
280
281             # for retval don't generate setters and generate retval check
282             if util.is_retval_field(c_name):
283                 err_handler = callback_err_handler_template.substitute(
284                     handler_name=handler_name
285                 )
286                 continue
287
288         handlers.append(msg_handler_template.substitute(
289             inputfile=inputfile,
290             api_data=util.api_message_to_javadoc(f),
291             handler_name=handler_name,
292             plugin_name=plugin_name,
293             dto_name=dto_name,
294             class_ref_name=ref_name,
295             dto_setters=dto_setters,
296             err_handler=err_handler))
297
298     return "\n".join(handlers)
299
300
301 handler_registration_template = Template("""_(${name}_${crc}, ${name}) \\
302 """)
303
304
305 def generate_handler_registration(func_list):
306     handler_registration = ["#define foreach_api_reply_handler \\\n"]
307     for f in func_list:
308         name = f['name']
309         camelcase_name = util.underscore_to_camelcase(f['name'])
310
311         if (not util.is_reply(camelcase_name) and not util.is_notification(name)) or util.is_ignored(name) \
312                 or util.is_control_ping(camelcase_name):
313             continue
314
315         handler_registration.append(handler_registration_template.substitute(
316             name=name,
317             crc=f['crc']))
318
319     return "".join(handler_registration)
320
321
322 api_verification_template = Template("""_(${name}_${crc}) \\
323 """)
324
325
326 def generate_api_verification(func_list):
327     api_verification = ["#define foreach_supported_api_message \\\n"]
328     for f in func_list:
329         name = f['name']
330
331         if util.is_ignored(name):
332             continue
333
334         api_verification.append(api_verification_template.substitute(
335             name=name,
336             crc=f['crc']))
337
338     return "".join(api_verification)
339
340
341 jvpp_c_template = Template("""/**
342  * This file contains JNI bindings for jvpp Java API.
343  * It was generated by jvpp_c_gen.py based on $inputfile
344  * (python representation of api file generated by vppapigen).
345  */
346
347 // JAVA class reference cache
348 $class_cache
349
350 // List of supported API messages used for verification
351 $api_verification
352
353 // JNI bindings
354 $jni_implementations
355
356 // Message handlers
357 $msg_handlers
358
359 // Registration of message handlers in vlib
360 $handler_registration
361 """)
362
363 def generate_jvpp(func_list, plugin_name, inputfile, path):
364     """ Generates jvpp C file """
365     print "Generating jvpp C"
366
367     class_cache = generate_class_cache(func_list, plugin_name)
368     jni_impl = generate_jni_impl(func_list, plugin_name, inputfile)
369     msg_handlers = generate_msg_handlers(func_list, plugin_name, inputfile)
370     handler_registration = generate_handler_registration(func_list)
371     api_verification = generate_api_verification(func_list)
372
373     jvpp_c_file = open("%s/jvpp_%s_gen.h" % (path, plugin_name), 'w')
374     jvpp_c_file.write(jvpp_c_template.substitute(
375             inputfile=inputfile,
376             class_cache=class_cache,
377             api_verification=api_verification,
378             jni_implementations=jni_impl,
379             msg_handlers=msg_handlers,
380             handler_registration=handler_registration))
381     jvpp_c_file.flush()
382     jvpp_c_file.close()
383