diff options
author | Damjan Marion <damarion@cisco.com> | 2018-07-30 16:10:14 +0200 |
---|---|---|
committer | Dave Barach <openvpp@barachs.net> | 2018-08-01 20:28:53 +0000 |
commit | cc4a5e8089967f0c266e9c5ed319c38c111004cd (patch) | |
tree | bc290e03e12a0a02c3fc274740c96eef9e1707c6 /extras/japi/java/jvpp | |
parent | a14c16674023bd6672ca49e3551c707702711050 (diff) |
Move java api to extras/
Change-Id: Ibd5cbbdfb22a235442cddaebc9eae9a3c4e35ec9
Signed-off-by: Damjan Marion <damarion@cisco.com>
Diffstat (limited to 'extras/japi/java/jvpp')
19 files changed, 3439 insertions, 0 deletions
diff --git a/extras/japi/java/jvpp/gen/jvpp_gen.py b/extras/japi/java/jvpp/gen/jvpp_gen.py new file mode 100755 index 00000000000..067a92f86be --- /dev/null +++ b/extras/japi/java/jvpp/gen/jvpp_gen.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import argparse +import logging +import os +import sys + +from jvppgen.types_gen import generate_types +from jvppgen.enums_gen import generate_enums +from jvppgen.unions_gen import generate_unions +from jvppgen.dto_gen import generate_dtos +from jvppgen.jvpp_ifc_gen import generate_java_ifc +from jvppgen.jvpp_impl_gen import generate_java_impl +from jvppgen.callback_gen import generate_callbacks +from jvppgen.jni_gen import generate_jni +from jvppgen.notification_gen import generate_notifications +from jvppgen.jvpp_future_facade_gen import generate_future_facade +from jvppgen.jvpp_callback_facade_gen import generate_callback_facade +from jvppgen.jvpp_model import JVppModel + + +def generate_jvpp(root_dir, model, logger): + base_dir = "%s/target/%s" % (root_dir, model.plugin_package.replace(".", "/")) + generate_types(_work_dir(base_dir, "types"), model, logger) + generate_enums(_work_dir(base_dir, "types"), model, logger) + generate_unions(_work_dir(base_dir, "types"), model, logger) + generate_dtos(_work_dir(base_dir, "dto"), model, logger) + generate_java_ifc(_work_dir(base_dir), model, logger) + generate_java_impl(_work_dir(base_dir), model, logger) + generate_callbacks(_work_dir(base_dir, "callback"), model, logger) + generate_jni(root_dir, model, logger) + generate_notifications(_work_dir(base_dir, "notification"), model, logger) + generate_future_facade(_work_dir(base_dir, "future"), model, logger) + generate_callback_facade(_work_dir(base_dir, "callfacade"), model, logger) + + +def _work_dir(work_dir, sub_dir=None): + if sub_dir: + work_dir = "%s/%s" % (work_dir, sub_dir) + try: + os.makedirs(work_dir) + except OSError: + if not os.path.isdir(work_dir): + raise + return work_dir + + +def _init_logger(): + try: + verbose = int(os.getenv("V", 0)) + except: + verbose = 0 + + log_level = logging.WARNING + if verbose == 1: + log_level = logging.INFO + elif verbose >= 2: + log_level = logging.DEBUG + + logging.basicConfig(stream=sys.stdout, level=log_level) + logger = logging.getLogger("JVPP GEN") + logger.setLevel(log_level) + return logger + + +if __name__ == '__main__': + logger = _init_logger() + + argparser = argparse.ArgumentParser(description="VPP Java API generator") + argparser.add_argument('-i', nargs='+', metavar='api_file.json', help="json vpp api file(s)") + argparser.add_argument('--plugin_name') + argparser.add_argument('--root_dir') + args = argparser.parse_args() + + logger.info("Generating Java API for %s" % args.i) + logger.debug("plugin_name: %s" % args.plugin_name) + logger.debug("root_dir: %s" % args.root_dir) + + model = JVppModel(logger, args.i, args.plugin_name) + generate_jvpp(args.root_dir, model, logger) diff --git a/extras/japi/java/jvpp/gen/jvppgen/__init__.py b/extras/japi/java/jvpp/gen/jvppgen/__init__.py new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/extras/japi/java/jvpp/gen/jvppgen/__init__.py diff --git a/extras/japi/java/jvpp/gen/jvppgen/callback_gen.py b/extras/japi/java/jvpp/gen/jvppgen/callback_gen.py new file mode 100755 index 00000000000..14aa8b760a5 --- /dev/null +++ b/extras/japi/java/jvpp/gen/jvppgen/callback_gen.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from string import Template + +from jvpp_model import is_request, is_dump, is_control_ping, is_control_ping_reply + + +def generate_callbacks(work_dir, model, logger): + json_api_files = model.json_api_files + logger.debug("Generating Callback interfaces for %s" % json_api_files) + plugin_package = model.plugin_package + + callbacks = [] + for msg in model.messages: + name = msg.java_name_upper + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control_ping managed by jvpp registry. + continue + if is_dump(msg) or is_request(msg): + continue + + callbacks.append("%s.callback.%sCallback" % (plugin_package, name)) + callback = _CALLBACK_TEMPLATE.substitute( + plugin_package=plugin_package, + json_filename=json_api_files, + name=name) + + with open("%s/%sCallback.java" % (work_dir, name), "w") as f: + f.write(callback) + + plugin_name = model.plugin_java_name + with open("%s/JVpp%sGlobalCallback.java" % (work_dir, plugin_name), "w") as f: + f.write(_GLOBAL_CALLBACK_TEMPLATE.substitute( + plugin_package=plugin_package, + json_filename=json_api_files, + plugin_name=plugin_name, + callbacks=", ".join(callbacks) + )) + +_CALLBACK_TEMPLATE = Template("""package $plugin_package.callback; + +/** + * <p>Represents callback for plugin's api message. + * <br>It was generated by jvpp_callback_gen.py based on $json_filename. + */ +public interface ${name}Callback extends io.fd.vpp.jvpp.callback.JVppCallback { + + void on${name}(${plugin_package}.dto.${name} reply); +} +""") + +_GLOBAL_CALLBACK_TEMPLATE = Template("""package $plugin_package.callback; + +/** + * <p>Global aggregated callback interface. + * <br>It was generated by jvpp_callback_gen.py based on $json_filename. + */ +public interface JVpp${plugin_name}GlobalCallback extends io.fd.vpp.jvpp.callback.ControlPingCallback, $callbacks { +} +""") diff --git a/extras/japi/java/jvpp/gen/jvppgen/dto_gen.py b/extras/japi/java/jvpp/gen/jvppgen/dto_gen.py new file mode 100755 index 00000000000..ca015a30456 --- /dev/null +++ b/extras/japi/java/jvpp/gen/jvppgen/dto_gen.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from string import Template + +from jvpp_common_gen import generate_hash_code, generate_equals, generate_to_string, generate_fields +from jvpp_model import is_request, is_reply, is_retval, is_dump, is_details, is_event, is_control_ping, \ + is_control_ping_reply + + +def generate_dtos(work_dir, model, logger): + logger.debug("Generating DTOs for %s " % model.json_api_files) + _generate_message_dtos(work_dir, model, logger) + _generate_dump_reply_wrappers(work_dir, model, logger) + + +def _generate_message_dtos(work_dir, model, logger): + for msg in model.messages: + logger.debug("Generating DTO for message %s", msg) + class_name = msg.java_name_upper + + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control_ping managed by jvpp registry. + continue + if is_request(msg): + dto = _generate_request_dto(msg, model, base_type="JVppRequest") + elif is_dump(msg): + dto = _generate_request_dto(msg, model, base_type="JVppDump") + elif is_reply(msg) or is_details(msg): + dto = _generate_reply_dto(msg, model) + elif is_event(msg): + dto = _generate_event_dto(msg, model) + else: + logger.warn("Failed to generate DTO for: %s. Message type is not supported." % msg) + continue + with open("%s/%s.java" % (work_dir, class_name), "w") as f: + f.write(dto) + + +def _generate_request_dto(msg, model, base_type): + msg_java_name_upper = msg.java_name_upper + fields = msg.fields + return _REQUEST_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + json_definition=msg.doc, + class_name=msg_java_name_upper, + base_type=base_type, + fields=generate_fields(fields), + hash_code=generate_hash_code(fields), + equals=generate_equals(msg_java_name_upper, fields), + to_string=generate_to_string(msg_java_name_upper, fields), + send=_generate_send(model, msg)) + +_REQUEST_TEMPLATE = Template(""" +package $plugin_package.dto; + +/** + * <p>This class represents request DTO. + * <br>It was generated by dto_gen.py based on $json_filename: + * <pre> +$json_definition + * </pre> + */ +public final class $class_name implements io.fd.vpp.jvpp.dto.$base_type { +$fields +$hash_code +$equals +$to_string +$send +} +""") + + +def _generate_send(model, msg): + return _SEND_TEMPLATE.substitute( + plugin_package=model.plugin_package, + plugin_name=model.plugin_java_name, + method_name=msg.java_name_lower, + args="this" if msg.has_fields else "" + ) + +_SEND_TEMPLATE = Template(""" + @Override + public int send(final io.fd.vpp.jvpp.JVpp jvpp) throws io.fd.vpp.jvpp.VppInvocationException { + return (($plugin_package.JVpp${plugin_name})jvpp).$method_name($args); + }""") + + +def _generate_reply_dto(msg, model): + msg_java_name_upper = msg.java_name_upper + # Negative retval is mapped to java exception, so filter it out: + fields = filter(lambda field: not is_retval(field), msg.fields) + return _REPLY_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + json_definition=msg.doc, + class_name=msg_java_name_upper, + request_name=msg.request_java, + fields=generate_fields(fields), + hash_code=generate_hash_code(fields), + equals=generate_equals(msg_java_name_upper, fields), + to_string=generate_to_string(msg_java_name_upper, fields)) + +_REPLY_TEMPLATE = Template(""" +package $plugin_package.dto; + +/** + * <p>This class represents reply DTO. + * <br>It was generated by jvpp_dto_gen.py based on $json_filename: + * <pre> +$json_definition + * </pre> + */ +public final class $class_name implements io.fd.vpp.jvpp.dto.JVppReply<$plugin_package.dto.$request_name> { +$fields +$hash_code +$equals +$to_string +} +""") + + +def _generate_event_dto(msg, model): + msg_java_name_upper = msg.java_name_upper + # Negative retval is mapped to java exception, so filter it out: + fields = filter(lambda field: not is_retval(field), msg.fields) + return _EVENT_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + json_definition=msg.doc, + class_name=msg_java_name_upper, + fields=generate_fields(fields), + hash_code=generate_hash_code(fields), + equals=generate_equals(msg_java_name_upper, fields), + to_string=generate_to_string(msg_java_name_upper, fields)) + +_EVENT_TEMPLATE = Template(""" +package $plugin_package.dto; + +/** + * <p>This class represents event DTO. + * <br>It was generated by jvpp_dto_gen.py based on $json_filename: + * <pre> +$json_definition + * </pre> + */ +public final class $class_name { +$fields +$hash_code +$equals +$to_string +} +""") + + +def _generate_dump_reply_wrappers(work_dir, model, logger): + for msg in model.messages: + if is_details(msg): + logger.debug("Generating ReplyDump DTO for message %s", msg) + details_class = msg.java_name_upper + dto = _REPLY_DUMP_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + json_definition=msg.doc, + details_class=details_class, + details_field=msg.java_name_lower, + dump_class=msg.request_java + ) + with open("%s/%sReplyDump.java" % (work_dir, details_class), "w") as f: + f.write(dto) + +_REPLY_DUMP_TEMPLATE = Template(""" +package $plugin_package.dto; + +/** + * <p>This class represents dump reply wrapper. + * <br>It was generated by jvpp_dto_gen.py based on $json_filename: + * <pre> +$json_definition + * </pre> + */ +public final class ${details_class}ReplyDump implements io.fd.vpp.jvpp.dto.JVppReplyDump<${plugin_package}.dto.${dump_class}, ${plugin_package}.dto.${details_class}> { + + public java.util.List<${details_class}> ${details_field} = new java.util.ArrayList<>(); + + @Override + @io.fd.vpp.jvpp.coverity.SuppressFBWarnings("UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD") + public int hashCode() { + return java.util.Objects.hash(${details_field}); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final ${details_class}ReplyDump other = (${details_class}ReplyDump) o; + + if (!java.util.Objects.equals(this.${details_field}, other.${details_field})) { + return false; + } + + return true; + } + + @Override + public String toString() { + return "${details_class}ReplyDump{" + + "${details_field}=" + ${details_field} + "}"; + } + + +} +""") diff --git a/extras/japi/java/jvpp/gen/jvppgen/enums_gen.py b/extras/japi/java/jvpp/gen/jvppgen/enums_gen.py new file mode 100755 index 00000000000..8ba96558428 --- /dev/null +++ b/extras/japi/java/jvpp/gen/jvppgen/enums_gen.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from string import Template + +from jvpp_model import Enum + + +def generate_enums(work_dir, model, logger): + logger.debug("Generating enums for %s " % model.json_api_files) + + for t in model.types: + if not isinstance(t, Enum): + continue + logger.debug("Generating DTO for enum %s", t) + type_class_name = t.java_name + type_class = _ENUM_TEMPLATE.substitute( + plugin_package=model.plugin_package, + c_type_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + java_enum_name=type_class_name, + constants=_generate_constants(t.constants), + value_type=t.value.type.java_name + ) + with open("%s/%s.java" % (work_dir, type_class_name), "w") as f: + f.write(type_class) + +_ENUM_TEMPLATE = Template(""" +package $plugin_package.types; + +/** + * <p>This class represents $c_type_name enum definition. + * <br>It was generated by enums_gen.py based on $json_filename: + * <pre> +$json_definition + * </pre> + */ +public enum $java_enum_name { +$constants; + + public final $value_type value; + + $java_enum_name(final $value_type value) { + this.value = value; + } + + public static $java_enum_name forValue(final $value_type value) { + for ($java_enum_name enumeration : $java_enum_name.values()) { + if (value == enumeration.value) { + return enumeration; + } + } + return null; + } +} +""") + + +def _generate_constants(constants): + return ",\n".join(_CONSTANT_TEMPLATE.substitute(name=c['name'], value=c['value']) for c in constants) + +_CONSTANT_TEMPLATE = Template(""" $name($value)""") diff --git a/extras/japi/java/jvpp/gen/jvppgen/jni_common_gen.py b/extras/japi/java/jvpp/gen/jvppgen/jni_common_gen.py new file mode 100755 index 00000000000..397b92b528f --- /dev/null +++ b/extras/japi/java/jvpp/gen/jvppgen/jni_common_gen.py @@ -0,0 +1,427 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from string import Template + +from jvpp_model import is_array, is_retval, Class, Enum, Union + + +def generate_j2c_identifiers(element, class_ref_name, object_ref_name): + identifiers = [] + for field in element.fields: + field_type = field.type + identifiers.append(_REQUEST_FIELD_IDENTIFIER_TEMPLATE.substitute( + java_name=field.java_name, + class_ref_name=class_ref_name, + jni_signature=field_type.jni_signature, + jni_type=field_type.jni_type, + jni_accessor=field_type.jni_accessor, + object_ref_name=object_ref_name + )) + return "".join(identifiers) + +_REQUEST_FIELD_IDENTIFIER_TEMPLATE = Template(""" + jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}, "${java_name}", "${jni_signature}"); + ${jni_type} ${java_name} = (*env)->Get${jni_accessor}Field(env, ${object_ref_name}, ${java_name}FieldId); +""") + + +# TODO(VPP-1187): do not inline JNI object creation inside message handlers to reduce number of special cases +def generate_j2c_swap(element, struct_ref_name): + initialization = [] + for field in element.fields: + initialization.append(generate_j2c_field_swap(field, struct_ref_name)) + return "\n".join(initialization) + + +def generate_j2c_field_swap(field, struct_ref_name): + if is_array(field): + return _generate_j2c_array_swap(field, struct_ref_name) + else: + return _generate_j2c_scalar_swap(field, struct_ref_name) + + +def _generate_j2c_array_swap(field, struct_ref_name): + # TODO(VPP-1186): move the logic to JNI generators + base_type = field.type.base_type + if isinstance(base_type, (Class, Enum, Union)): + return _generate_j2c_object_array_swap(field, struct_ref_name) + elif base_type.is_swap_needed: + return _generate_j2c_primitive_type_array_swap(field, struct_ref_name) + else: + return _generate_j2c_primitive_type_array_no_swap(field, struct_ref_name) + + +def _generate_j2c_object_array_swap(field, struct_ref_name): + field_type = field.type + field_reference_name = field.java_name + c_name = field.name + host = "%sArrayElement" % field_reference_name + net = "%s->%s[_i]" % (struct_ref_name, c_name) + swap_elements = field_type.get_host_to_net_function(host, net) + return _J2C_OBJECT_ARRAY_SWAP_TEMPLATE.substitute( + field_reference_name=field_reference_name, + field_length_check=_generate_field_length_check(field), + swap_elements=swap_elements) + +_J2C_OBJECT_ARRAY_SWAP_TEMPLATE = Template(""" + { + if (${field_reference_name}) { + size_t _i; + jsize cnt = (*env)->GetArrayLength (env, ${field_reference_name}); + ${field_length_check} + for (_i = 0; _i < cnt; _i++) { + jobject ${field_reference_name}ArrayElement = (*env)->GetObjectArrayElement(env, ${field_reference_name}, _i); + ${swap_elements}; + } + } + } +""") + + +def _generate_j2c_primitive_type_array_swap(field, struct_ref_name): + field_reference_name = field.java_name + field_type = field.type + host = "%sArrayElements[_i]" % field_reference_name + net = "%s->%s[_i]" % (struct_ref_name, field.name) + swap_elements = field_type.get_host_to_net_function(host, net) + return _J2C_PRIMITIVE_TYPE_ARRAY_SWAP_TEMPLATE.substitute( + field_reference_name=field_reference_name, + field_length_check=_generate_field_length_check(field), + base_type=field_type.base_type.jni_accessor, + jni_base_type=field_type.base_type.jni_type, + swap_elements=swap_elements + ) + +_J2C_PRIMITIVE_TYPE_ARRAY_SWAP_TEMPLATE = Template(""" + if (${field_reference_name}) { + ${jni_base_type} * ${field_reference_name}ArrayElements = (*env)->Get${base_type}ArrayElements(env, ${field_reference_name}, NULL); + size_t _i; + jsize cnt = (*env)->GetArrayLength(env, ${field_reference_name}); + ${field_length_check} + for (_i = 0; _i < cnt; _i++) { + ${swap_elements}; + } + (*env)->Release${base_type}ArrayElements (env, ${field_reference_name}, ${field_reference_name}ArrayElements, 0); + } + """) + + +def _generate_j2c_primitive_type_array_no_swap(field, struct_ref_name): + field_type = field.type + return _J2C_PRIMITIVE_TYPE_ARRAY_NO_SWAP_TEMPLATE.substitute( + field_reference_name=field.java_name, + field_length_check=_generate_field_length_check(field), + base_type=field_type.base_type.jni_accessor, + jni_base_type=field_type.base_type.jni_type, + struct_reference_name=struct_ref_name, + c_name=field.name + ) + +_J2C_PRIMITIVE_TYPE_ARRAY_NO_SWAP_TEMPLATE = Template(""" + if (${field_reference_name}) { + jsize cnt = (*env)->GetArrayLength (env, ${field_reference_name}); + ${field_length_check} + (*env)->Get${base_type}ArrayRegion(env, ${field_reference_name}, 0, cnt, (${jni_base_type} *)${struct_reference_name}->${c_name}); + } +""") + + +def _generate_field_length_check(field): + # Enforce max length if array has fixed length or uses variable length syntax + field_length = str(field.array_len) + if field.array_len_field: + field_length = field.array_len_field.java_name + + # TODO: remove when ZLAs without length field are disabled + if field_length != "0": + return _FIELD_LENGTH_CHECK.substitute(field_length=field_length) + else: + return "" + +# Make sure we do not write more elements that are expected +_FIELD_LENGTH_CHECK = Template(""" + size_t max_size = ${field_length}; + if (cnt > max_size) cnt = max_size;""") + + +def _generate_j2c_scalar_swap(field, struct_ref_name): + field_type = field.type + if field_type.is_swap_needed: + host = field.java_name + net = "%s->%s" % (struct_ref_name, field.name) + return " %s;" % field_type.get_host_to_net_function(host, net) + else: + return " %s->%s = %s;" % (struct_ref_name, field.name, field.java_name) + + +def generate_c2j_swap(element, object_ref_name, struct_ref_name): + msg_java_name = element.java_name_lower + initialization = [] + for field in element.fields: + if is_retval(field): + # For retval don't generate setters and generate retval check + continue + elif is_array(field): + initialization.append(_generate_c2j_array_swap(msg_java_name, field, object_ref_name, struct_ref_name)) + else: + initialization.append(_generate_c2j_scalar_swap(msg_java_name, field, object_ref_name, struct_ref_name)) + return "".join(initialization) + + +def _generate_c2j_array_swap(msg_java_name, field, object_ref_name, struct_ref_name): + # TODO(VPP-1186): move the logic to JNI generators + base_type = field.type.base_type + if isinstance(base_type, (Class, Union)): + return _generate_c2j_object_array_swap(msg_java_name, field, object_ref_name, struct_ref_name) + elif isinstance(base_type, Enum): + return _generate_c2j_enum_array_swap(msg_java_name, field, object_ref_name, struct_ref_name) + elif base_type.is_swap_needed: + return _generate_c2j_primitive_type_array_swap(msg_java_name, field, object_ref_name, struct_ref_name) + else: + return _generate_c2j_primitive_type_array_no_swap(msg_java_name, field, object_ref_name, struct_ref_name) + + +def _generate_c2j_object_array_swap(msg_java_name, field, object_ref_name, struct_ref_name): + field_type = field.type + return _C2J_OBJECT_ARRAY_SWAP_TEMPLATE.substitute( + field_reference_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_name=field_type.base_type.jni_name, + field_length=_generate_array_length(field, struct_ref_name), + net_to_host_function=field_type.net_to_host_function, + struct_ref_name=struct_ref_name, + object_ref_name=object_ref_name, + c_name=field.name + ) + +_C2J_OBJECT_ARRAY_SWAP_TEMPLATE = Template(""" + jfieldID ${field_reference_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${field_reference_name}", "${jni_signature}"); + { + jclass ${field_reference_name}Class = (*env)->FindClass(env, "${jni_name}"); + jobjectArray ${field_reference_name} = (*env)->NewObjectArray(env, ${field_length}, ${field_reference_name}Class, 0); + jmethodID ${field_reference_name}Constructor = (*env)->GetMethodID(env, ${field_reference_name}Class, "<init>", "()V"); + unsigned int _i; + for (_i = 0; _i < ${field_length}; _i++) { + jobject ${field_reference_name}ArrayElement = (*env)->NewObject(env, ${field_reference_name}Class, ${field_reference_name}Constructor); + ${net_to_host_function}(env, &(${struct_ref_name}->${c_name}[_i]), ${field_reference_name}ArrayElement); + (*env)->SetObjectArrayElement(env, ${field_reference_name}, _i, ${field_reference_name}ArrayElement); + (*env)->DeleteLocalRef(env, ${field_reference_name}ArrayElement); + } + (*env)->SetObjectField(env, ${object_ref_name}, ${field_reference_name}FieldId, ${field_reference_name}); + (*env)->DeleteLocalRef(env, ${field_reference_name}); + } +""") + + +def _generate_c2j_enum_array_swap(msg_java_name, field, object_ref_name, struct_ref_name): + field_type = field.type + base_type = field_type.base_type + return _C2J_ENUM_ARRAY_SWAP_TEMPLATE.substitute( + field_reference_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_name=base_type.jni_name, + field_length=_generate_array_length(field, struct_ref_name), + net_to_host_function=field_type.net_to_host_function, + jni_signature_enum_value=base_type.value.type.jni_signature, + struct_ref_name=struct_ref_name, + object_ref_name=object_ref_name, + c_name=field.name + ) + +_C2J_ENUM_ARRAY_SWAP_TEMPLATE = Template(""" + jfieldID ${field_reference_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${field_reference_name}", "${jni_signature}"); + { + jclass ${field_reference_name}Class = (*env)->FindClass(env, "${jni_name}"); + jobjectArray ${field_reference_name} = (*env)->NewObjectArray(env, ${field_length}, ${field_reference_name}Class, 0); + jmethodID ${field_reference_name}Constructor = (*env)->GetStaticMethodID(env, ${field_reference_name}Class, "forValue", "(${jni_signature_enum_value})${jni_signature}"); + unsigned int _i; + for (_i = 0; _i < ${field_length}; _i++) { + jobject ${field_reference_name}ArrayElement = (*env)->CallStaticObjectMethod(env, ${field_reference_name}Class, ${field_reference_name}Constructor, ${net_to_host_function}(${struct_ref_name}->${c_name}[_i])); + (*env)->SetObjectArrayElement(env, ${field_reference_name}, _i, ${field_reference_name}ArrayElement); + (*env)->DeleteLocalRef(env, ${field_reference_name}ArrayElement); + } + (*env)->SetObjectField(env, ${object_ref_name}, ${field_reference_name}FieldId, ${field_reference_name}); + (*env)->DeleteLocalRef(env, ${field_reference_name}); + } +""") + + +def _generate_c2j_primitive_type_array_swap(msg_java_name, field, object_ref_name, struct_ref_name): + field_type = field.type + return _C2J_PRIMITIVE_TYPE_ARRAY_SWAP_TEMPLATE.substitute( + field_reference_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_type=field_type.jni_type, + base_type=field_type.base_type.jni_accessor, + field_length=_generate_array_length(field, struct_ref_name), + jni_base_type=field_type.base_type.jni_type, + object_ref_name=object_ref_name, + struct_ref_name=struct_ref_name, + net_to_host_function=field_type.net_to_host_function, + c_name=field.name + ) + +_C2J_PRIMITIVE_TYPE_ARRAY_SWAP_TEMPLATE = Template(""" + jfieldID ${field_reference_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${field_reference_name}", "${jni_signature}"); + { + ${jni_type} ${field_reference_name} = (*env)->New${base_type}Array(env, ${field_length}); + ${jni_base_type} * ${field_reference_name}ArrayElements = (*env)->Get${base_type}ArrayElements(env, ${field_reference_name}, NULL); + unsigned int _i; + for (_i = 0; _i < ${field_length}; _i++) { + ${field_reference_name}ArrayElements[_i] = ${net_to_host_function}(${struct_ref_name}->${c_name}[_i]); + } + + (*env)->Release${base_type}ArrayElements(env, ${field_reference_name}, ${field_reference_name}ArrayElements, 0); + (*env)->SetObjectField(env, ${object_ref_name}, ${field_reference_name}FieldId, ${field_reference_name}); + (*env)->DeleteLocalRef(env, ${field_reference_name}); + } +""") + + +def _generate_c2j_primitive_type_array_no_swap(msg_java_name, field, object_ref_name, struct_ref_name): + field_type = field.type + return _C2J_PRIMITIVE_TYPE_ARRAY_NO_SWAP_TEMPLATE.substitute( + field_reference_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_type=field_type.jni_type, + base_type=field_type.base_type.jni_accessor, + field_length=_generate_array_length(field, struct_ref_name), + jni_base_type=field_type.base_type.jni_type, + object_ref_name=object_ref_name, + struct_ref_name=struct_ref_name, + c_name=field.name + ) + +_C2J_PRIMITIVE_TYPE_ARRAY_NO_SWAP_TEMPLATE = Template(""" + jfieldID ${field_reference_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${field_reference_name}", "${jni_signature}"); + ${jni_type} ${field_reference_name} = (*env)->New${base_type}Array(env, ${field_length}); + (*env)->Set${base_type}ArrayRegion(env, ${field_reference_name}, 0, ${field_length}, (const ${jni_base_type}*)${struct_ref_name}->${c_name}); + (*env)->SetObjectField(env, ${object_ref_name}, ${field_reference_name}FieldId, ${field_reference_name}); + (*env)->DeleteLocalRef(env, ${field_reference_name}); +""") + + +def _generate_array_length(field, struct_ref_name): + if field.array_len_field: + len_field = field.array_len_field + if len_field.type.is_swap_needed: + return "%s(%s->%s)" % (len_field.type.host_to_net_function, struct_ref_name, len_field.name) + else: + return "%s->%s" % (struct_ref_name, len_field.name) + return field.array_len + + +def _generate_c2j_scalar_swap(msg_java_name, field, object_ref_name, struct_ref_name): + field_type = field.type + if field_type.is_swap_needed: + # TODO(VPP-1186): move the logic to JNI generators + if isinstance(field_type, (Class, Union)): + return _generate_c2j_object_swap(msg_java_name, field, object_ref_name, struct_ref_name) + elif isinstance(field_type, Enum): + return _generate_c2j_enum_swap(msg_java_name, field, object_ref_name, struct_ref_name) + else: + return _generate_c2j_primitive_type_swap(msg_java_name, field, object_ref_name, struct_ref_name) + else: + return _generate_c2j_primitive_type_no_swap(msg_java_name, field, object_ref_name, struct_ref_name) + + +def _generate_c2j_object_swap(msg_java_name, field, object_ref_name, struct_ref_name): + field_type = field.type + return _C2J_OBJECT_SWAP_TEMPLATE.substitute( + java_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_name=field_type.jni_name, + jni_accessor=field_type.jni_accessor, + object_ref_name=object_ref_name, + struct_ref_name=struct_ref_name, + net_to_host_function=field_type.net_to_host_function, + c_name=field.name) + +_C2J_OBJECT_SWAP_TEMPLATE = Template(""" + jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${java_name}", "${jni_signature}"); + jclass ${java_name}Class = (*env)->FindClass(env, "${jni_name}"); + jmethodID ${java_name}Constructor = (*env)->GetMethodID(env, ${java_name}Class, "<init>", "()V"); + jobject ${java_name} = (*env)->NewObject(env, ${java_name}Class, ${java_name}Constructor); + ${net_to_host_function}(env, &(${struct_ref_name}->${c_name}), ${java_name}); + (*env)->Set${jni_accessor}Field(env, ${object_ref_name}, ${java_name}FieldId, ${java_name}); + (*env)->DeleteLocalRef(env, ${java_name}); +""") + + +def _generate_c2j_enum_swap(msg_java_name, field, object_ref_name, struct_ref_name): + field_type = field.type + return _C2J_ENUM_SWAP_TEMPLATE.substitute( + java_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_signature_enum_value=field_type.value.type.jni_signature, + jni_name=field_type.jni_name, + jni_accessor=field_type.jni_accessor, + object_ref_name=object_ref_name, + struct_ref_name=struct_ref_name, + net_to_host_function=field_type.net_to_host_function, + c_name=field.name) + +_C2J_ENUM_SWAP_TEMPLATE = Template(""" + jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${java_name}", "${jni_signature}"); + jclass ${java_name}Class = (*env)->FindClass(env, "${jni_name}"); + jmethodID ${java_name}Constructor = (*env)->GetStaticMethodID(env, ${java_name}Class, "forValue", "(${jni_signature_enum_value})${jni_signature}"); + jobject ${java_name} = (*env)->CallStaticObjectMethod(env, ${java_name}Class, ${java_name}Constructor, ${net_to_host_function}(${struct_ref_name}->${c_name})); + (*env)->SetObjectField(env, ${object_ref_name}, ${java_name}FieldId, ${java_name}); + (*env)->DeleteLocalRef(env, ${java_name}); +""") + + +def _generate_c2j_primitive_type_swap(msg_java_name, field, object_ref_name, struct_ref_name): + field_type = field.type + return _C2J_PRIMITIVE_TYPE_SWAP_TEMPLATE.substitute( + java_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_accessor=field_type.jni_accessor, + object_ref_name=object_ref_name, + net_to_host_function=field_type.net_to_host_function, + struct_ref_name=struct_ref_name, + c_name=field.name + ) + +_C2J_PRIMITIVE_TYPE_SWAP_TEMPLATE = Template(""" + jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${java_name}", "${jni_signature}"); + (*env)->Set${jni_accessor}Field(env, ${object_ref_name}, ${java_name}FieldId, ${net_to_host_function}(${struct_ref_name}->${c_name})); +""") + + +def _generate_c2j_primitive_type_no_swap(msg_java_name, field, object_ref_name, struct_ref_name): + field_type = field.type + return _C2J_PRIMITIVE_TYPE_NO_SWAP_TEMPLATE.substitute( + java_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_accessor=field_type.jni_accessor, + object_ref_name=object_ref_name, + struct_ref_name=struct_ref_name, + c_name=field.name + ) + +_C2J_PRIMITIVE_TYPE_NO_SWAP_TEMPLATE = Template(""" + jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${java_name}", "${jni_signature}"); + (*env)->Set${jni_accessor}Field(env, ${object_ref_name}, ${java_name}FieldId, ${struct_ref_name}->${c_name}); +""") diff --git a/extras/japi/java/jvpp/gen/jvppgen/jni_gen.py b/extras/japi/java/jvpp/gen/jvppgen/jni_gen.py new file mode 100755 index 00000000000..ad6c2614062 --- /dev/null +++ b/extras/japi/java/jvpp/gen/jvppgen/jni_gen.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from string import Template + +from jni_impl_gen import generate_jni_impl +from jni_msg_handlers_gen import generate_jni_handlers +from jni_type_handlers_gen import generate_type_handlers +from jvpp_model import is_control_ping, is_dump, is_request, is_control_ping_reply + + +def generate_jni(work_dir, model, logger): + logger.debug("Generating jvpp C for %s" % model.json_api_files) + plugin_name = model.plugin_name + messages = model.messages + + with open("%s/jvpp_%s_gen.h" % (work_dir, plugin_name), "w") as f: + f.write(_JVPP_C_TEMPLATE.substitute( + json_filename=model.json_api_files, + class_cache=_generate_class_cache(plugin_name, messages), + api_verification=_generate_api_verification(messages), + type_handlers=generate_type_handlers(model, logger), + jni_implementations=generate_jni_impl(model), + msg_handlers=generate_jni_handlers(model), + handler_registration=_generate_handler_registration(messages))) + +_JVPP_C_TEMPLATE = Template("""/** + * This file contains JNI bindings for jvpp Java API. + * It was generated by jvpp_jni_gen.py based on $json_filename. + */ +$class_cache + +$api_verification + +// Type handlers +$type_handlers + +// JNI bindings +$jni_implementations + +// Message handlers +$msg_handlers + +$handler_registration +""") + + +def _generate_class_cache(plugin_name, messages): + references = [] + for msg in messages: + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control_ping managed by jvpp registry. + continue + references.append(( + msg.java_name_lower, + 'io/fd/vpp/jvpp/%s/dto/%s' % (plugin_name, msg.java_name_upper) + )) + + references.append(('callbackException', 'io/fd/vpp/jvpp/VppCallbackException')) + + return _CLASS_CACHE_TEMPLATE.substitute( + class_references=_generate_class_references(references), + create_references=_generate_create_references(references), + delete_references=_generate_delete_references(references) + ) + +_CLASS_CACHE_TEMPLATE = Template(""" +// JAVA class reference cache +$class_references + +static int cache_class_references(JNIEnv* env) { +$create_references + return 0; +} + +static void delete_class_references(JNIEnv* env) { +$delete_references +}""") + + +def _generate_class_references(references): + return "\n".join("jclass %sClass;" % r[0] for r in references) + + +def _generate_create_references(references): + items = [] + for r in references: + items.append(_CREATE_GLOBAL_REF_TEMPLATE.substitute( + ref_name=r[0], + fqn_name=r[1] + )) + return "".join(items) + +_CREATE_GLOBAL_REF_TEMPLATE = Template(""" + ${ref_name}Class = (jclass)(*env)->NewGlobalRef(env, (*env)->FindClass(env, "${fqn_name}")); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + return JNI_ERR; + }""") + + +def _generate_delete_references(references): + items = [] + for r in references: + items.append(_DELETE_CLASS_INVOCATION_TEMPLATE.substitute(ref_name=r[0])) + return "".join(items) + +_DELETE_CLASS_INVOCATION_TEMPLATE = Template(""" + if (${ref_name}Class) { + (*env)->DeleteGlobalRef(env, ${ref_name}Class); + }""") + + +def _generate_api_verification(messages): + items = [] + for msg in messages: + items.append("_(%s_%s) \\" % (msg.name, msg.crc)) + return _API_VERIFICATION_TEMPLATE.substitute(messages="\n".join(items)) + +_API_VERIFICATION_TEMPLATE = Template(""" +// List of supported API messages used for verification +#define foreach_supported_api_message \\ +$messages +""") + + +def _generate_handler_registration(messages): + """ + Generates msg handler registration for all messages except for dumps and requests. + :param messages: collection of VPP API messages. + """ + handlers = [] + for msg in messages: + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control_ping managed by jvpp registry. + continue + if is_dump(msg) or is_request(msg): + continue + name = msg.name + crc = msg.crc + handlers.append("_(%s_%s, %s) \\" % (name, crc, name)) + return _HANDLER_REGISTRATION_TEMPLATE.substitute(handlers="\n".join(handlers)) + +_HANDLER_REGISTRATION_TEMPLATE = Template(""" +// Registration of message handlers in vlib +#define foreach_api_reply_handler \\ +$handlers +""") diff --git a/extras/japi/java/jvpp/gen/jvppgen/jni_impl_gen.py b/extras/japi/java/jvpp/gen/jvppgen/jni_impl_gen.py new file mode 100755 index 00000000000..bf7523670a4 --- /dev/null +++ b/extras/japi/java/jvpp/gen/jvppgen/jni_impl_gen.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from string import Template + +from jni_common_gen import generate_j2c_identifiers, generate_j2c_swap +from jvpp_model import is_dump, is_request, is_control_ping, is_control_ping_reply + + +def generate_jni_impl(model): + """ + Generates JNI bindings for sending dump and request messages. + :param model: meta-model of VPP API used for jVPP generation. + """ + jni_impl = [] + for msg in model.messages: + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control ping managed by jvpp registry. + continue + if not (is_dump(msg) or is_request(msg)): + continue + arguments = "" + request_class = "" + jni_identifiers = "" + msg_initialization = "" + + if msg.has_fields: + arguments = ", jobject request" + request_class = _REQUEST_CLASS_TEMPLATE.substitute( + plugin_name=model.plugin_name, + java_dto_name=msg.java_name_upper + ) + jni_identifiers = generate_j2c_identifiers(msg, class_ref_name="requestClass", object_ref_name="request") + msg_initialization = generate_j2c_swap(msg, struct_ref_name="mp") + + jni_impl.append(_JNI_IMPL_TEMPLATE.substitute( + c_name=msg.name, + json_filename=model.json_api_files, + json_definition=msg.doc, + plugin_name=model.plugin_name, + plugin_java_name=model.plugin_java_name, + java_method_name=msg.java_name_lower, + arguments=arguments, + request_class=request_class, + jni_identifiers=jni_identifiers, + msg_size=_generate_msg_size(msg), + crc=msg.crc, + msg_initialization=msg_initialization + )) + return "".join(jni_impl) + + +_JNI_IMPL_TEMPLATE = Template(""" +/** + * JNI binding for sending ${c_name} message. + * Generated based on $json_filename: +$json_definition + */ +JNIEXPORT jint JNICALL Java_io_fd_vpp_jvpp_${plugin_name}_JVpp${plugin_java_name}Impl_${java_method_name}0 +(JNIEnv * env, jclass clazz${arguments}) { + ${plugin_name}_main_t *plugin_main = &${plugin_name}_main; + vl_api_${c_name}_t * mp; + u32 my_context_id = vppjni_get_context_id (&jvpp_main); +$request_class +$jni_identifiers + + // create message: + const size_t _size = ${msg_size}; + mp = vl_msg_api_alloc(_size); + memset (mp, 0, _size); + mp->_vl_msg_id = ntohs (get_message_id(env, "${c_name}_${crc}")); + mp->client_index = plugin_main->my_client_index; + mp->context = clib_host_to_net_u32 (my_context_id); + +$msg_initialization + + // send message: + if (CLIB_DEBUG > 1) + clib_warning ("Sending ${c_name} message"); + vl_msg_api_send_shmem (plugin_main->vl_input_queue, (u8 *)&mp); + if ((*env)->ExceptionCheck(env)) { + return JNI_ERR; + } + return my_context_id; +}""") + +# TODO: cache method and field identifiers to achieve better performance +# https://jira.fd.io/browse/HONEYCOMB-42 +_REQUEST_CLASS_TEMPLATE = Template(""" jclass requestClass = (*env)->FindClass(env, "io/fd/vpp/jvpp/${plugin_name}/dto/${java_dto_name}"); +""") + + +def _generate_msg_size(msg): + msg_size = "sizeof(*mp)" + _size_components = [] + for field in msg.fields: + # Ignore ZLAs for simplicity (to support them we need to call JNI functions to check actual size) + if field.array_len_field: + _size_components += " + %s*sizeof(%s)" % (field.array_len_field.java_name, field.type.base_type.vpp_name) + # FIXME(VPP-586): for proper nested structures support, we need generate functions computing type sizes + # and use it instead of sizeof + + return msg_size + "".join(_size_components) diff --git a/extras/japi/java/jvpp/gen/jvppgen/jni_msg_handlers_gen.py b/extras/japi/java/jvpp/gen/jvppgen/jni_msg_handlers_gen.py new file mode 100755 index 00000000000..8f6410f1af2 --- /dev/null +++ b/extras/japi/java/jvpp/gen/jvppgen/jni_msg_handlers_gen.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from string import Template + +from jni_common_gen import generate_c2j_swap +from jvpp_model import is_dump, is_request, is_control_ping, is_control_ping_reply, is_retval + + +def generate_jni_handlers(model): + """ + Generates msg handlers for all messages except for dumps and requests (handled by vpp, not client). + :param model: meta-model of VPP API used for jVPP generation. + """ + jni_impl = [] + for msg in model.messages: + msg_name = msg.name + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control ping managed by jvpp registry. + continue + if is_dump(msg) or is_request(msg): + continue + + jni_impl.append(_MSG_HANDLER_TEMPLATE.substitute( + c_name=msg_name, + json_filename=model.json_api_files, + json_definition=msg.doc, + plugin_name=model.plugin_name, + err_handler=_generate_error_handler(msg), + class_ref_name=msg.java_name_lower, + dto_name=msg.java_name_upper, + dto_setters=generate_c2j_swap(msg, object_ref_name="dto", struct_ref_name="mp") + )) + return "".join(jni_impl) + +_MSG_HANDLER_TEMPLATE = Template(""" +/** + * Handler for ${c_name} message. + * Generated based on $json_filename: +$json_definition + */ +static void vl_api_${c_name}_t_handler (vl_api_${c_name}_t * mp) +{ + ${plugin_name}_main_t *plugin_main = &${plugin_name}_main; + JNIEnv *env = jvpp_main.jenv; + jthrowable exc; +$err_handler + + if (CLIB_DEBUG > 1) + clib_warning ("Received ${c_name} event message"); + + jmethodID constructor = (*env)->GetMethodID(env, ${class_ref_name}Class, "<init>", "()V"); + + // User does not have to provide callbacks for all VPP messages. + // We are ignoring messages that are not supported by user. + (*env)->ExceptionClear(env); // just in case exception occurred in different place and was not properly cleared + jmethodID callbackMethod = (*env)->GetMethodID(env, plugin_main->callbackClass, "on${dto_name}", "(Lio/fd/vpp/jvpp/${plugin_name}/dto/${dto_name};)V"); + exc = (*env)->ExceptionOccurred(env); + if (exc) { + clib_warning("Unable to extract on${dto_name} method reference from ${plugin_name} plugin's callbackClass. Ignoring message.\\n"); + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + return; + } + + jobject dto = (*env)->NewObject(env, ${class_ref_name}Class, constructor); +$dto_setters + + (*env)->CallVoidMethod(env, plugin_main->callbackObject, callbackMethod, dto); + // free DTO as per http://stackoverflow.com/questions/1340938/memory-leak-when-calling-java-code-from-c-using-jni + (*env)->DeleteLocalRef(env, dto); +}""") + + +def _generate_error_handler(msg): + err_handler = "" + for field in msg.fields: + if is_retval(field): + err_handler = _ERR_HANDLER_TEMPLATE.substitute(name=msg.name) + return err_handler + +# Code fragment for checking result of the operation before sending request reply. +# Error checking is optional (some messages, e.g. detail messages do not have retval field). +_ERR_HANDLER_TEMPLATE = Template(""" + // for negative result don't send callback message but send error callback + if (mp->retval<0) { + call_on_error("${name}", mp->context, mp->retval, plugin_main->callbackClass, plugin_main->callbackObject, callbackExceptionClass); + return; + } + if (mp->retval == VNET_API_ERROR_IN_PROGRESS) { + clib_warning("Result in progress"); + return; + }""") diff --git a/extras/japi/java/jvpp/gen/jvppgen/jni_type_handlers_gen.py b/extras/japi/java/jvpp/gen/jvppgen/jni_type_handlers_gen.py new file mode 100755 index 00000000000..a76aadf468c --- /dev/null +++ b/extras/japi/java/jvpp/gen/jvppgen/jni_type_handlers_gen.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from string import Template + +from jni_common_gen import generate_j2c_swap, generate_j2c_field_swap, generate_j2c_identifiers, generate_c2j_swap +from jvpp_model import Class, Enum, Union + + +def generate_type_handlers(model, logger): + """ + Generates host-to-net and net-to-host functions for all custom types defined in the VPP API + :param model: meta-model of VPP API used for jVPP generation. + :param logger: jVPP logger + """ + type_handlers = [] + for t in model.types: + #TODO(VPP-1186): move the logic to JNI generators + if isinstance(t, Class): + _generate_class(model, t, type_handlers) + elif isinstance(t, Enum): + _generate_enum(model, t, type_handlers) + elif isinstance(t, Union): + _generate_union(model, t, type_handlers) + else: + logger.debug("Skipping custom JNI type handler generation for %s", t) + + return "\n".join(type_handlers) + + +def _generate_class(model, t, type_handlers): + ref_name = t.java_name_lower + type_handlers.append(_TYPE_HOST_TO_NET_TEMPLATE.substitute( + c_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + type_reference_name=ref_name, + class_FQN=t.jni_name, + jni_identifiers=generate_j2c_identifiers(t, class_ref_name="%sClass" % ref_name, object_ref_name="_host"), + type_swap=generate_j2c_swap(t, struct_ref_name="_net") + )) + type_handlers.append(_TYPE_NET_TO_HOST_TEMPLATE.substitute( + c_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + type_reference_name=ref_name, + class_FQN=t.jni_name, + type_swap=generate_c2j_swap(t, object_ref_name="_host", struct_ref_name="_net") + )) + +_TYPE_HOST_TO_NET_TEMPLATE = Template(""" +/** + * Host to network byte order conversion for ${c_name} type. + * Generated based on $json_filename: +$json_definition + */ +static inline void _host_to_net_${c_name}(JNIEnv * env, jobject _host, vl_api_${c_name}_t * _net) +{ + jclass ${type_reference_name}Class = (*env)->FindClass(env, "${class_FQN}"); +$jni_identifiers +$type_swap +}""") + +_TYPE_NET_TO_HOST_TEMPLATE = Template(""" +/** + * Network to host byte order conversion for ${c_name} type. + * Generated based on $json_filename: +$json_definition + */ +static inline void _net_to_host_${c_name}(JNIEnv * env, vl_api_${c_name}_t * _net, jobject _host) +{ + jclass ${type_reference_name}Class = (*env)->FindClass(env, "${class_FQN}"); +$type_swap +}""") + + +def _generate_enum(model, t, type_handlers): + value_type = t.value.type + type_handlers.append(_ENUM_NET_TO_HOST_TEMPLATE.substitute( + c_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + class_FQN=t.jni_name, + jni_signature=value_type.jni_signature, + jni_type=value_type.jni_type, + jni_accessor=value_type.jni_accessor, + swap=_generate_scalar_host_to_net_swap(t.value) + )) + + type_handlers.append(_ENUM_HOST_TO_NET_TEMPLATE.substitute( + c_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + class_FQN=t.jni_name, + jni_type=value_type.jni_type, + type_swap=_generate_scalar_net_to_host_swap(t.value) + )) + +_ENUM_NET_TO_HOST_TEMPLATE = Template(""" +/** + * Host to network byte order conversion for ${c_name} enum. + * Generated based on $json_filename: +$json_definition + */ +static inline void _host_to_net_${c_name}(JNIEnv * env, jobject _host, vl_api_${c_name}_t * _net) +{ + jclass enumClass = (*env)->FindClass(env, "${class_FQN}"); + jfieldID valueFieldId = (*env)->GetStaticFieldID(env, enumClass, "value", "${jni_signature}"); + ${jni_type} value = (*env)->GetStatic${jni_accessor}Field(env, enumClass, valueFieldId); + ${swap}; +}""") + +_ENUM_HOST_TO_NET_TEMPLATE = Template(""" +/** + * Network to host byte order conversion for ${c_name} type. + * Generated based on $json_filename: +$json_definition + */ +static inline ${jni_type} _net_to_host_${c_name}(vl_api_${c_name}_t _net) +{ + return (${jni_type}) $type_swap +}""") + + +def _generate_scalar_host_to_net_swap(field): + field_type = field.type + if field_type.is_swap_needed: + return field_type.get_host_to_net_function(field.java_name, "*_net") + else: + return "*_net = %s" % field.java_name + + +def _generate_scalar_net_to_host_swap(field): + field_type = field.type + if field_type.is_swap_needed: + return "%s((%s) _net);" % (field_type.net_to_host_function, field_type.name) + else: + return "_net" + + +def _generate_union(model, t, type_handlers): + type_handlers.append(_generate_union_host_to_net(model, t)) + type_handlers.append(_generate_union_net_to_host(model, t)) + + +def _generate_union_host_to_net(model, t): + swap = [] + for i, field in enumerate(t.fields): + field_type = field.type + swap.append(_UNION_FIELD_HOST_TO_NET_TEMPLATE.substitute( + field_index=i, + java_name=field.java_name, + jni_signature=field_type.jni_signature, + jni_type=field_type.jni_type, + jni_accessor=field_type.jni_accessor, + swap=generate_j2c_field_swap(field, struct_ref_name="_net") + )) + + return _UNION_HOST_TO_NET_TEMPLATE.substitute( + c_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + class_FQN=t.jni_name, + swap="".join(swap) + ) + +_UNION_FIELD_HOST_TO_NET_TEMPLATE = Template(""" + if (_activeMember == ${field_index}) { + jfieldID fieldId = (*env)->GetFieldID(env, _class, "${java_name}", "${jni_signature}"); + ${jni_type} ${java_name} = (*env)->Get${jni_accessor}Field(env, _host, fieldId); + ${swap} + }""") + +_UNION_HOST_TO_NET_TEMPLATE = Template(""" +/** + * Host to network byte order conversion for ${c_name} union. + * Generated based on $json_filename: +$json_definition + */ +static inline void _host_to_net_${c_name}(JNIEnv * env, jobject _host, vl_api_${c_name}_t * _net) +{ + jclass _class = (*env)->FindClass(env, "${class_FQN}"); + + jfieldID _activeMemberFieldId = (*env)->GetFieldID(env, _class, "_activeMember", "I"); + jint _activeMember = (*env)->GetIntField(env, _host, _activeMemberFieldId); +$swap +}""") + + +def _generate_union_net_to_host(model, t): + return _UNION_NET_TO_HOST_TEMPLATE.substitute( + c_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + type_reference_name=t.java_name_lower, + class_FQN=t.jni_name, + swap=generate_c2j_swap(t, object_ref_name="_host", struct_ref_name="_net") + ) + +_UNION_NET_TO_HOST_TEMPLATE = Template(""" +/** + * Network to host byte order conversion for ${c_name} union. + * Generated based on $json_filename: +$json_definition + */ +static inline void _net_to_host_${c_name}(JNIEnv * env, vl_api_${c_name}_t * _net, jobject _host) +{ + jclass ${type_reference_name}Class = (*env)->FindClass(env, "${class_FQN}"); +$swap +}""") diff --git a/extras/japi/java/jvpp/gen/jvppgen/jvpp_callback_facade_gen.py b/extras/japi/java/jvpp/gen/jvppgen/jvpp_callback_facade_gen.py new file mode 100644 index 00000000000..c5634de2e48 --- /dev/null +++ b/extras/japi/java/jvpp/gen/jvppgen/jvpp_callback_facade_gen.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from string import Template + +from jvpp_model import is_control_ping, is_dump, is_request, is_event, is_control_ping_reply + + +def generate_callback_facade(work_dir, model, logger): + """ Generates callback facade """ + logger.debug("Generating JVpp callback facade for %s" % model.json_api_files) + _generate_ifc(work_dir, model), + _generate_impl(work_dir, model) + _generate_callback(work_dir, model) + + +def _generate_ifc(work_dir, model): + with open("%s/CallbackJVpp%s.java" % (work_dir, model.plugin_java_name), "w") as f: + f.write(_IFC_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + plugin_name=model.plugin_java_name, + methods=_generate_ifc_methods(model) + )) + +_IFC_TEMPLATE = Template(""" +package $plugin_package.callfacade; + +/** + * <p>Callback Java API representation of $plugin_package plugin. + * <br>It was generated by jvpp_callback_facade_gen.py based on $json_filename. + */ +public interface CallbackJVpp${plugin_name} extends io.fd.vpp.jvpp.notification.EventRegistryProvider, java.lang.AutoCloseable { + + // TODO add send + +$methods +} +""") + + +def _generate_ifc_methods(model): + plugin_package = model.plugin_package + methods = [] + for msg in model.messages: + if is_control_ping(msg): + # Skip control ping managed by jvpp registry. + continue + if not (is_dump(msg) or is_request(msg)): + # Skip replies and messages that do not not have replies (e.g events/counters). + continue + template = _IFC_NO_ARG_METHOD_TEMPLATE + if msg.has_fields: + template = _IFC_METHOD_TEMPLATE + methods.append(template.substitute( + name=msg.java_name_lower, + plugin_package=plugin_package, + request=msg.java_name_upper, + reply=msg.reply_java + )) + return "\n".join(methods) + +_IFC_NO_ARG_METHOD_TEMPLATE = Template( + """ void $name($plugin_package.callback.${reply}Callback callback) throws io.fd.vpp.jvpp.VppInvocationException;""") + +_IFC_METHOD_TEMPLATE = Template( + """ void $name($plugin_package.dto.$request request, $plugin_package.callback.${reply}Callback callback) throws io.fd.vpp.jvpp.VppInvocationException;""") + + +def _generate_impl(work_dir, model): + with open("%s/CallbackJVpp%sFacade.java" % (work_dir, model.plugin_java_name), "w") as f: + f.write(_IMPL_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + plugin_name=model.plugin_java_name, + methods=_generate_impl_methods(model) + )) + +_IMPL_TEMPLATE = Template(""" +package $plugin_package.callfacade; + +/** + * <p>Default implementation of Callback${plugin_name}JVpp interface. + * <br>It was generated by jvpp_callback_facade_gen.py based on $json_filename. + */ +public final class CallbackJVpp${plugin_name}Facade implements CallbackJVpp${plugin_name} { + + private final $plugin_package.JVpp${plugin_name} jvpp; + private final java.util.Map<Integer, io.fd.vpp.jvpp.callback.JVppCallback> callbacks; + private final $plugin_package.notification.${plugin_name}EventRegistryImpl eventRegistry = new $plugin_package.notification.${plugin_name}EventRegistryImpl(); + /** + * <p>Create CallbackJVpp${plugin_name}Facade object for provided JVpp instance. + * Constructor internally creates CallbackJVppFacadeCallback class for processing callbacks + * and then connects to provided JVpp instance + * + * @param jvpp provided io.fd.vpp.jvpp.JVpp instance + * + * @throws java.io.IOException in case instance cannot connect to JVPP + */ + public CallbackJVpp${plugin_name}Facade(final io.fd.vpp.jvpp.JVppRegistry registry, final $plugin_package.JVpp${plugin_name} jvpp) throws java.io.IOException { + this.jvpp = java.util.Objects.requireNonNull(jvpp,"jvpp is null"); + this.callbacks = new java.util.HashMap<>(); + java.util.Objects.requireNonNull(registry, "JVppRegistry should not be null"); + registry.register(jvpp, new CallbackJVpp${plugin_name}FacadeCallback(this.callbacks, eventRegistry)); + } + + @Override + public $plugin_package.notification.${plugin_name}EventRegistry getEventRegistry() { + return eventRegistry; + } + + @Override + public void close() throws Exception { + jvpp.close(); + } + + // TODO add send() + +$methods +} +""") + + +def _generate_impl_methods(model): + plugin_package = model.plugin_package + methods = [] + for msg in model.messages: + if is_control_ping(msg): + # Skip control ping managed by jvpp registry. + continue + if not (is_dump(msg) or is_request(msg)): + # Skip replies and messages that do not not have replies (e.g events/counters). + continue + template = _IMPL_NO_ARG_METHOD_TEMPLATE + if msg.has_fields: + template = _IMPL_METHOD_TEMPLATE + methods.append(template.substitute( + name=msg.java_name_lower, + plugin_package=plugin_package, + request=msg.java_name_upper, + reply=msg.reply_java + )) + return "\n".join(methods) + +_IMPL_NO_ARG_METHOD_TEMPLATE = Template( + """ public final void $name($plugin_package.callback.${reply}Callback callback) throws io.fd.vpp.jvpp.VppInvocationException { + synchronized (callbacks) { + callbacks.put(jvpp.$name(), callback); + } + } +""") + +_IMPL_METHOD_TEMPLATE = Template(""" public final void $name($plugin_package.dto.$request request, $plugin_package.callback.${reply}Callback callback) throws io.fd.vpp.jvpp.VppInvocationException { + synchronized (callbacks) { + callbacks.put(jvpp.$name(request), callback); + } + } +""") + + +def _generate_callback(work_dir, model): + with open("%s/CallbackJVpp%sFacadeCallback.java" % (work_dir, model.plugin_java_name), "w") as f: + f.write(_CALLBACK_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + plugin_name=model.plugin_java_name, + methods=_generate_callback_methods(model) + )) + +_CALLBACK_TEMPLATE = Template(""" +package $plugin_package.callfacade; + +/** + * <p>Implementation of JVppGlobalCallback interface for Java Callback API. + * <br>It was generated by jvpp_callback_facade_gen.py based on $json_filename. + */ +public final class CallbackJVpp${plugin_name}FacadeCallback implements $plugin_package.callback.JVpp${plugin_name}GlobalCallback { + + private final java.util.Map<Integer, io.fd.vpp.jvpp.callback.JVppCallback> requests; + private final $plugin_package.notification.Global${plugin_name}EventCallback eventCallback; + private static final java.util.logging.Logger LOG = java.util.logging.Logger.getLogger(CallbackJVpp${plugin_name}FacadeCallback.class.getName()); + + public CallbackJVpp${plugin_name}FacadeCallback(final java.util.Map<Integer, io.fd.vpp.jvpp.callback.JVppCallback> requestMap, + final $plugin_package.notification.Global${plugin_name}EventCallback eventCallback) { + this.requests = requestMap; + this.eventCallback = eventCallback; + } + + @Override + public void onError(io.fd.vpp.jvpp.VppCallbackException reply) { + + io.fd.vpp.jvpp.callback.JVppCallback failedCall; + synchronized(requests) { + failedCall = requests.remove(reply.getCtxId()); + } + + if(failedCall != null) { + try { + failedCall.onError(reply); + } catch(RuntimeException ex) { + ex.addSuppressed(reply); + LOG.log(java.util.logging.Level.WARNING, String.format("Callback: %s failed while handling exception: %s", failedCall, reply), ex); + } + } + } + + @Override + @SuppressWarnings("unchecked") + public void onControlPingReply(final io.fd.vpp.jvpp.dto.ControlPingReply reply) { + + io.fd.vpp.jvpp.callback.ControlPingCallback callback; + final int replyId = reply.context; + synchronized(requests) { + callback = (io.fd.vpp.jvpp.callback.ControlPingCallback) requests.remove(replyId); + } + + if(callback != null) { + callback.onControlPingReply(reply); + } + } + +$methods +} +""") + + +def _generate_callback_methods(model): + plugin_package = model.plugin_package + methods = [] + for msg in model.messages: + if is_dump(msg) or is_request(msg): + continue + if is_control_ping_reply(msg): + # Skip control ping managed by jvpp registry. + continue + + # Generate callbacks for all messages except for dumps and requests (handled by vpp, not client). + template = _CALLBACK_METHOD_TEMPLATE + if is_event(msg): + template = _CALLBACK_EVENT_METHOD_TEMPLATE + msg_name = msg.java_name_upper + methods.append(template.substitute( + message=msg_name, + callback="%sCallback" % msg_name, + plugin_package=plugin_package + )) + return "\n".join(methods) + +_CALLBACK_METHOD_TEMPLATE = Template(""" + @Override + @SuppressWarnings("unchecked") + public void on${message}(final $plugin_package.dto.${message} reply) { + + $plugin_package.callback.$callback callback; + final int replyId = reply.context; + if (LOG.isLoggable(java.util.logging.Level.FINE)) { + LOG.fine(String.format("Received ${message} event message: %s", reply)); + } + synchronized(requests) { + callback = ($plugin_package.callback.$callback) requests.remove(replyId); + } + + if(callback != null) { + callback.on${message}(reply); + } + } +""") + +_CALLBACK_EVENT_METHOD_TEMPLATE = Template(""" + @Override + @SuppressWarnings("unchecked") + public void on${message}($plugin_package.dto.${message} notification) { + if (LOG.isLoggable(java.util.logging.Level.FINE)) { + LOG.fine(String.format("Received ${message} event message: %s", notification)); + } + eventCallback.on${message}(notification); + } +""") diff --git a/extras/japi/java/jvpp/gen/jvppgen/jvpp_common_gen.py b/extras/japi/java/jvpp/gen/jvppgen/jvpp_common_gen.py new file mode 100755 index 00000000000..83226ea78ac --- /dev/null +++ b/extras/japi/java/jvpp/gen/jvppgen/jvpp_common_gen.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from string import Template + +from jvpp_model import is_array + + +def generate_fields(fields, access_modifier="public"): + return "\n".join(_FIELD_TEMPLATE + .substitute(access_modifier=access_modifier, type=f.type.java_name_fqn, name=f.java_name) + for f in fields) + +_FIELD_TEMPLATE = Template(""" ${access_modifier} ${type} ${name};""") + + +def generate_hash_code(fields): + if len(fields) == 1 and is_array(fields[0]): + return _HASH_CODE_SINGLE_ARRAY_TEMPLATE.substitute(array_field=fields[0].java_name) + return _HASH_CODE_TEMPLATE.substitute(fields=", ".join(f.java_name for f in fields)) + +_HASH_CODE_TEMPLATE = Template(""" + @Override + @io.fd.vpp.jvpp.coverity.SuppressFBWarnings("UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD") + public int hashCode() { + return java.util.Objects.hash($fields); + }""") + +_HASH_CODE_SINGLE_ARRAY_TEMPLATE = Template(""" + @Override + @io.fd.vpp.jvpp.coverity.SuppressFBWarnings("UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD") + public int hashCode() { + return java.util.Arrays.hashCode($array_field); + }""") + + +def generate_equals(class_name, fields): + comparisons = [] + for f in fields: + if is_array(f): + comparisons.append(_EQUALS_ARRAY_FIELD_TEMPLATE.substitute(field_name=f.java_name)) + else: + comparisons.append(_EQUALS_FIELD_TEMPLATE.substitute(field_name=f.java_name)) + + if comparisons: + comparisons.insert(0, _EQUALS_OTHER_TEMPLATE.substitute(cls_name=class_name)) + return _EQUALS_TEMPLATE.substitute(comparisons="\n".join(comparisons)) + +_EQUALS_TEMPLATE = Template(""" + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } +$comparisons + + return true; + }""") + +_EQUALS_OTHER_TEMPLATE = Template(""" + final $cls_name other = ($cls_name) o; +""") + +_EQUALS_FIELD_TEMPLATE = Template(""" if (!java.util.Objects.equals(this.$field_name, other.$field_name)) { + return false; + }""") + +_EQUALS_ARRAY_FIELD_TEMPLATE = Template(""" if (!java.util.Arrays.equals(this.$field_name, other.$field_name)) { + return false; + }""") + + +def generate_to_string(class_name, fields): + to_string = [] + for f in fields: + if is_array(f): + to_string.append(_TO_STRING_ARRAY_FIELD_TEMPLATE.substitute(field_name=f.java_name)) + else: + to_string.append(_TO_STRING_FIELD_TEMPLATE.substitute(field_name=f.java_name)) + + to_string_fields = " \"}\";" + if to_string: + to_string_fields = " + \", \" +\n".join(to_string) + " + \"}\";" + + return _TO_STRING_TEMPLATE.substitute( + class_name=class_name, + to_string_fields=to_string_fields + ) + +_TO_STRING_TEMPLATE = Template(""" + @Override + public String toString() { + return "$class_name{" + +$to_string_fields + }""") + +_TO_STRING_FIELD_TEMPLATE = Template(""" \"$field_name=\" + $field_name""") + +_TO_STRING_ARRAY_FIELD_TEMPLATE = Template( + """ \"$field_name=\" + java.util.Arrays.toString($field_name)""") diff --git a/extras/japi/java/jvpp/gen/jvppgen/jvpp_future_facade_gen.py b/extras/japi/java/jvpp/gen/jvppgen/jvpp_future_facade_gen.py new file mode 100644 index 00000000000..47a9985144d --- /dev/null +++ b/extras/japi/java/jvpp/gen/jvppgen/jvpp_future_facade_gen.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from string import Template + +from jvpp_model import is_control_ping, is_control_ping_reply, is_dump, is_request, is_details, is_reply, is_event + + +def generate_future_facade(work_dir, model, logger): + logger.debug("Generating JVpp future facade for %s" % model.json_api_files) + _generate_future_jvpp(work_dir, model), + _generate_future_jvpp_facade(work_dir, model) + _generate_future_jvpp_callback(work_dir, model) + + +def _generate_future_jvpp(work_dir, model): + with open("%s/FutureJVpp%s.java" % (work_dir, model.plugin_java_name), "w") as f: + f.write(_FUTURE_JVPP_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + plugin_name=model.plugin_java_name, + methods=_generate_future_jvpp_methods(model) + )) + +_FUTURE_JVPP_TEMPLATE = Template(''' +package $plugin_package.future; + +/** + * <p>Async facade extension adding specific methods for each request invocation + * <br>It was generated by jvpp_future_facade_gen.py based on $json_filename. + */ +public interface FutureJVpp${plugin_name} extends io.fd.vpp.jvpp.future.FutureJVppInvoker { +$methods + + @Override + public $plugin_package.notification.${plugin_name}EventRegistry getEventRegistry(); + +} +''') + + +def _generate_future_jvpp_methods(model): + methods = [] + for msg in model.messages: + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control_ping managed by jvpp registry. + continue + reply_name = None + if is_request(msg): + reply_name = msg.reply_java + elif is_dump(msg): + # use reply dump wrappers + reply_name = "%sReplyDump" % msg.reply_java + else: + continue + + methods.append(_FUTURE_JVPP_METHOD_TEMPLATE.substitute( + plugin_package=model.plugin_package, + method_name=msg.java_name_lower, + reply_name=reply_name, + request_name=msg.java_name_upper + )) + return "".join(methods) + +_FUTURE_JVPP_METHOD_TEMPLATE = Template(''' + java.util.concurrent.CompletionStage<${plugin_package}.dto.${reply_name}> ${method_name}(${plugin_package}.dto.${request_name} request); +''') + + +def _generate_future_jvpp_facade(work_dir, model): + with open("%s/FutureJVpp%sFacade.java" % (work_dir, model.plugin_java_name), "w") as f: + f.write(_FUTURE_JVPP_FACADE_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + plugin_name=model.plugin_java_name, + methods=_generate_future_jvpp_facade_methods(model) + )) + +_FUTURE_JVPP_FACADE_TEMPLATE = Template(''' +package $plugin_package.future; + +/** + * <p>Implementation of FutureJVpp based on AbstractFutureJVppInvoker + * <br>It was generated by jvpp_future_facade_gen.py based on $json_filename. + */ +public class FutureJVpp${plugin_name}Facade extends io.fd.vpp.jvpp.future.AbstractFutureJVppInvoker implements FutureJVpp${plugin_name} { + + private final $plugin_package.notification.${plugin_name}EventRegistryImpl eventRegistry = new $plugin_package.notification.${plugin_name}EventRegistryImpl(); + + /** + * <p>Create FutureJVpp${plugin_name}Facade object for provided JVpp instance. + * Constructor internally creates FutureJVppFacadeCallback class for processing callbacks + * and then connects to provided JVpp instance + * + * @param jvpp provided io.fd.vpp.jvpp.JVpp instance + * + * @throws java.io.IOException in case instance cannot connect to JVPP + */ + public FutureJVpp${plugin_name}Facade(final io.fd.vpp.jvpp.JVppRegistry registry, final io.fd.vpp.jvpp.JVpp jvpp) throws java.io.IOException { + super(jvpp, registry, new java.util.HashMap<>()); + java.util.Objects.requireNonNull(registry, "JVppRegistry should not be null"); + registry.register(jvpp, new FutureJVpp${plugin_name}FacadeCallback(getRequests(), eventRegistry)); + } + + @Override + public $plugin_package.notification.${plugin_name}EventRegistry getEventRegistry() { + return eventRegistry; + } + +$methods +} +''') + + +def _generate_future_jvpp_facade_methods(model): + methods = [] + for msg in model.messages: + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control_ping managed by jvpp registry. + continue + template = None + if is_request(msg): + template = _FUTURE_JVPP_FACADE_REQUEST_TEMPLATE + elif is_dump(msg): + template = _FUTURE_JVPP_FACADE_DUMP_TEMPLATE + else: + continue + + methods.append(template.substitute( + plugin_package=model.plugin_package, + method_name=msg.java_name_lower, + reply_name=msg.reply_java, + request_name=msg.java_name_upper + )) + return "".join(methods) + +_FUTURE_JVPP_FACADE_REQUEST_TEMPLATE = Template(''' + @Override + public java.util.concurrent.CompletionStage<${plugin_package}.dto.${reply_name}> ${method_name}(${plugin_package}.dto.${request_name} request) { + return send(request); + } +''') + +_FUTURE_JVPP_FACADE_DUMP_TEMPLATE = Template(''' + @Override + public java.util.concurrent.CompletionStage<${plugin_package}.dto.${reply_name}ReplyDump> ${method_name}(${plugin_package}.dto.${request_name} request) { + return send(request, new ${plugin_package}.dto.${reply_name}ReplyDump()); + } +''') + + +def _generate_future_jvpp_callback(work_dir, model): + with open("%s/FutureJVpp%sFacadeCallback.java" % (work_dir, model.plugin_java_name), "w") as f: + f.write(_FUTURE_JVPP_CALLBACK_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + plugin_name=model.plugin_java_name, + methods=_generate_future_jvpp_callback_methods(model) + )) + +_FUTURE_JVPP_CALLBACK_TEMPLATE = Template(""" +package $plugin_package.future; + +/** + * <p>Async facade callback setting values to future objects + * <br>It was generated by jvpp_future_facade_gen.py based on $json_filename. + */ +public final class FutureJVpp${plugin_name}FacadeCallback implements $plugin_package.callback.JVpp${plugin_name}GlobalCallback { + + private final java.util.Map<java.lang.Integer, java.util.concurrent.CompletableFuture<? extends io.fd.vpp.jvpp.dto.JVppReply<?>>> requests; + private final $plugin_package.notification.Global${plugin_name}EventCallback notificationCallback; + private static final java.util.logging.Logger LOG = java.util.logging.Logger.getLogger(FutureJVpp${plugin_name}FacadeCallback.class.getName()); + + public FutureJVpp${plugin_name}FacadeCallback( + final java.util.Map<java.lang.Integer, java.util.concurrent.CompletableFuture<? extends io.fd.vpp.jvpp.dto.JVppReply<?>>> requestMap, + final $plugin_package.notification.Global${plugin_name}EventCallback notificationCallback) { + this.requests = requestMap; + this.notificationCallback = notificationCallback; + } + + @Override + @SuppressWarnings("unchecked") + public void onError(io.fd.vpp.jvpp.VppCallbackException reply) { + final java.util.concurrent.CompletableFuture<io.fd.vpp.jvpp.dto.JVppReply<?>> completableFuture; + + synchronized(requests) { + completableFuture = (java.util.concurrent.CompletableFuture<io.fd.vpp.jvpp.dto.JVppReply<?>>) requests.get(reply.getCtxId()); + } + + if(completableFuture != null) { + completableFuture.completeExceptionally(reply); + + synchronized(requests) { + requests.remove(reply.getCtxId()); + } + } + } + + @Override + @SuppressWarnings("unchecked") + public void onControlPingReply(final io.fd.vpp.jvpp.dto.ControlPingReply reply) { + java.util.concurrent.CompletableFuture<io.fd.vpp.jvpp.dto.JVppReply<?>> completableFuture; + + final int replyId = reply.context; + synchronized(requests) { + completableFuture = (java.util.concurrent.CompletableFuture<io.fd.vpp.jvpp.dto.JVppReply<?>>) requests.get(replyId); + + if(completableFuture != null) { + // Finish dump call + if (completableFuture instanceof io.fd.vpp.jvpp.future.AbstractFutureJVppInvoker.CompletableDumpFuture) { + completableFuture.complete(((io.fd.vpp.jvpp.future.AbstractFutureJVppInvoker.CompletableDumpFuture) completableFuture).getReplyDump()); + // Remove future mapped to dump call context id + requests.remove(((io.fd.vpp.jvpp.future.AbstractFutureJVppInvoker.CompletableDumpFuture) completableFuture).getContextId()); + } else { + // reply to regular control ping, complete the future + completableFuture.complete(reply); + } + requests.remove(replyId); + } else { + // future not yet created by writer, create new future, complete it and put to map under ping id + completableFuture = new java.util.concurrent.CompletableFuture<>(); + completableFuture.complete(reply); + requests.put(replyId, completableFuture); + } + } + } + +$methods +} +""") + + +def _generate_future_jvpp_callback_methods(model): + methods = [] + for msg in model.messages: + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control_ping managed by jvpp registry. + continue + if is_dump(msg) or is_request(msg): + continue + + # Generate callbacks for all messages except for dumps and requests (handled by vpp, not client). + template = None + request_dto = None + if is_details(msg): + template = _FUTURE_JVPP_FACADE_DETAILS_CALLBACK_TEMPLATE + request_dto = msg.request_java + elif is_reply(msg): + template = _FUTURE_JVPP_FACADE_REPLY_CALLBACK_TEMPLATE + request_dto = msg.request_java + elif is_event(msg): + template = _FUTURE_JVPP_FACADE_EVENT_CALLBACK_TEMPLATE + else: + raise TypeError("Unknown message type %s", msg) + + methods.append(template.substitute( + plugin_package=model.plugin_package, + callback_dto=msg.java_name_upper, + request_dto=request_dto, + callback_dto_field=msg.java_name_lower, + )) + return "".join(methods) + + +_FUTURE_JVPP_FACADE_DETAILS_CALLBACK_TEMPLATE = Template(""" + @Override + @SuppressWarnings("unchecked") + public void on$callback_dto(final $plugin_package.dto.$callback_dto reply) { + io.fd.vpp.jvpp.future.AbstractFutureJVppInvoker.CompletableDumpFuture<$plugin_package.dto.${callback_dto}ReplyDump> completableFuture; + final int replyId = reply.context; + if (LOG.isLoggable(java.util.logging.Level.FINE)) { + LOG.fine(String.format("Received $callback_dto event message: %s", reply)); + } + synchronized(requests) { + completableFuture = (io.fd.vpp.jvpp.future.AbstractFutureJVppInvoker.CompletableDumpFuture<$plugin_package.dto.${callback_dto}ReplyDump>) requests.get(replyId); + + if(completableFuture == null) { + // reply received before writer created future, + // create new future, and put into map to notify sender that reply is already received, + // following details replies will add information to this future + completableFuture = new io.fd.vpp.jvpp.future.AbstractFutureJVppInvoker.CompletableDumpFuture<>(replyId, + new $plugin_package.dto.${callback_dto}ReplyDump()); + requests.put(replyId, completableFuture); + } + completableFuture.getReplyDump().$callback_dto_field.add(reply); + } + } +""") + +_FUTURE_JVPP_FACADE_REPLY_CALLBACK_TEMPLATE = Template(""" + @Override + @SuppressWarnings("unchecked") + public void on$callback_dto(final $plugin_package.dto.$callback_dto reply) { + java.util.concurrent.CompletableFuture<io.fd.vpp.jvpp.dto.JVppReply<$plugin_package.dto.$request_dto>> completableFuture; + final int replyId = reply.context; + if (LOG.isLoggable(java.util.logging.Level.FINE)) { + LOG.fine(String.format("Received $callback_dto event message: %s", reply)); + } + synchronized(requests) { + completableFuture = + (java.util.concurrent.CompletableFuture<io.fd.vpp.jvpp.dto.JVppReply<$plugin_package.dto.$request_dto>>) requests.get(replyId); + + if(completableFuture != null) { + // received reply on request, complete future created by sender and remove it from map + completableFuture.complete(reply); + requests.remove(replyId); + } else { + // reply received before writer created future, + // create new future, complete it and put into map to + // notify sender that reply is already received + completableFuture = new java.util.concurrent.CompletableFuture<>(); + completableFuture.complete(reply); + requests.put(replyId, completableFuture); + } + } + } +""") + +_FUTURE_JVPP_FACADE_EVENT_CALLBACK_TEMPLATE = Template(""" + @Override + public void on$callback_dto($plugin_package.dto.$callback_dto notification) { + if (LOG.isLoggable(java.util.logging.Level.FINE)) { + LOG.fine(String.format("Received $callback_dto event message: %s", notification)); + } + notificationCallback.on$callback_dto(notification); + } +""") diff --git a/extras/japi/java/jvpp/gen/jvppgen/jvpp_ifc_gen.py b/extras/japi/java/jvpp/gen/jvppgen/jvpp_ifc_gen.py new file mode 100755 index 00000000000..e2b2922ed20 --- /dev/null +++ b/extras/japi/java/jvpp/gen/jvppgen/jvpp_ifc_gen.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from string import Template + +from jvpp_model import is_request, is_dump, is_event + + +def generate_java_ifc(work_dir, model, logger): + logger.debug("Generating JVpp interface for %s" % model.json_api_files) + messages = filter(_jvpp_ifc_filter, model.messages) + plugin_package = model.plugin_package + methods = [] + for msg in messages: + if msg.has_fields: + methods.append(_JVPP_IFC_METHOD_TEMPLATE.substitute( + name=msg.java_name_lower, + plugin_package=plugin_package, + type=msg.java_name_upper)) + else: + methods.append(_JVPP_IFC_NO_ARG_METHOD_TEMPLATE.substitute(name=msg.java_name_lower)) + + plugin_name = model.plugin_java_name + jvpp_interface = _JVPP_IFC_TEMPLATE.substitute( + plugin_package=plugin_package, + json_filename=model.json_api_files, + plugin_name=plugin_name, + methods="\n".join(methods) + ) + with open("%s/JVpp%s.java" % (work_dir, plugin_name), "w") as f: + f.write(jvpp_interface) + + +def _jvpp_ifc_filter(msg): + return is_request(msg) or is_dump(msg) or is_event(msg) + + +_JVPP_IFC_METHOD_TEMPLATE = Template( + """ int $name($plugin_package.dto.$type request) throws io.fd.vpp.jvpp.VppInvocationException;""") + +_JVPP_IFC_NO_ARG_METHOD_TEMPLATE = Template(""" int $name() throws io.fd.vpp.jvpp.VppInvocationException;""") + +_JVPP_IFC_TEMPLATE = Template("""package $plugin_package; + +/** + * <p>Java representation of plugin's api file. + * <br>It was generated by jvpp_impl_gen.py based on $json_filename. + * <br>(python representation of api file generated by vppapigen) + */ +public interface JVpp${plugin_name} extends io.fd.vpp.jvpp.JVpp { + /** + * Generic dispatch method for sending requests to VPP + * + * @throws io.fd.vpp.jvpp.VppInvocationException if send request had failed + */ + int send(io.fd.vpp.jvpp.dto.JVppRequest request) throws io.fd.vpp.jvpp.VppInvocationException; +$methods +} +""") diff --git a/extras/japi/java/jvpp/gen/jvppgen/jvpp_impl_gen.py b/extras/japi/java/jvpp/gen/jvppgen/jvpp_impl_gen.py new file mode 100755 index 00000000000..aff8d7fc99c --- /dev/null +++ b/extras/japi/java/jvpp/gen/jvppgen/jvpp_impl_gen.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from string import Template + +from jvpp_model import is_request, is_dump, is_event + + +def generate_java_impl(work_dir, model, logger): + logger.debug("Generating JVpp implementation for %s" % model.json_api_files) + messages = filter(_jvpp_impl_filter, model.messages) + plugin_package = model.plugin_package + methods = [] + for msg in messages: + if msg.has_fields: + methods.append(_JVPP_IMPL_METHOD_TEMPLATE.substitute( + name=msg.java_name_lower, + plugin_package=plugin_package, + type=msg.java_name_upper)) + else: + methods.append(_JVPP_IMPL_NO_ARG_METHOD_TEMPLATE.substitute( + name=msg.java_name_lower, + type=msg.java_name_upper)) + + plugin_name = model.plugin_java_name + jvpp_impl = _JVPP_IMPL_TEMPLATE.substitute( + plugin_package=plugin_package, + json_filename=model.json_api_files, + plugin_name=model.plugin_java_name, + plugin_name_underscore=model.plugin_name, + methods="\n".join(methods)) + + with open("%s/JVpp%sImpl.java" % (work_dir, plugin_name), "w") as f: + f.write(jvpp_impl) + + +def _jvpp_impl_filter(msg): + return is_request(msg) or is_dump(msg) or is_event(msg) + + +_JVPP_IMPL_TEMPLATE = Template("""package $plugin_package; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.Set; +import java.util.logging.Logger; +import java.util.logging.Level; +import io.fd.vpp.jvpp.callback.JVppCallback; +import io.fd.vpp.jvpp.VppConnection; +import io.fd.vpp.jvpp.JVppRegistry; + +/** + * <p>Default implementation of JVpp interface. + * <br>It was generated by jvpp_impl_gen.py based on $json_filename. + * <br>(python representation of api file generated by vppapigen) + */ +public final class JVpp${plugin_name}Impl implements $plugin_package.JVpp${plugin_name} { + + private final static Logger LOG = Logger.getLogger(JVpp${plugin_name}Impl.class.getName()); + private static final String LIBNAME = "libjvpp_${plugin_name_underscore}.so"; + + // FIXME using NativeLibraryLoader makes load fail could not find (WantInterfaceEventsReply). + static { + try { + loadLibrary(); + } catch (Exception e) { + LOG.severe("Can't find jvpp jni library: " + LIBNAME); + throw new ExceptionInInitializerError(e); + } + } + + private static void loadStream(final InputStream is) throws IOException { + final Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-x---"); + final Path p = Files.createTempFile(LIBNAME, null, PosixFilePermissions.asFileAttribute(perms)); + try { + Files.copy(is, p, StandardCopyOption.REPLACE_EXISTING); + + try { + Runtime.getRuntime().load(p.toString()); + } catch (UnsatisfiedLinkError e) { + throw new IOException("Failed to load library " + p, e); + } + } finally { + try { + Files.deleteIfExists(p); + } catch (IOException e) { + } + } + } + + private static void loadLibrary() throws IOException { + try (final InputStream is = JVpp${plugin_name}Impl.class.getResourceAsStream('/' + LIBNAME)) { + if (is == null) { + throw new IOException("Failed to open library resource " + LIBNAME); + } + loadStream(is); + } + } + + private VppConnection connection; + private JVppRegistry registry; + + private static native void init0(final JVppCallback callback, final long queueAddress, final int clientIndex); + @Override + public void init(final JVppRegistry registry, final JVppCallback callback, final long queueAddress, final int clientIndex) { + this.registry = java.util.Objects.requireNonNull(registry, "registry should not be null"); + this.connection = java.util.Objects.requireNonNull(registry.getConnection(), "connection should not be null"); + connection.checkActive(); + init0(callback, queueAddress, clientIndex); + } + + private static native void close0(); + @Override + public void close() { + close0(); + } + + @Override + public int send(io.fd.vpp.jvpp.dto.JVppRequest request) throws io.fd.vpp.jvpp.VppInvocationException { + return request.send(this); + } + + @Override + public final int controlPing(final io.fd.vpp.jvpp.dto.ControlPing controlPing) throws io.fd.vpp.jvpp.VppInvocationException { + return registry.controlPing(JVpp${plugin_name}Impl.class); + } +$methods +} +""") + +_JVPP_IMPL_METHOD_TEMPLATE = Template(""" + private static native int ${name}0($plugin_package.dto.$type request); + public final int $name($plugin_package.dto.$type request) throws io.fd.vpp.jvpp.VppInvocationException { + java.util.Objects.requireNonNull(request, "Null request object"); + connection.checkActive(); + if (LOG.isLoggable(Level.FINE)) { + LOG.fine(String.format("Sending $type event message: %s", request)); + } + int result=${name}0(request); + if (result<0){ + throw new io.fd.vpp.jvpp.VppInvocationException("${name}", result); + } + return result; + }""") + +_JVPP_IMPL_NO_ARG_METHOD_TEMPLATE = Template(""" + private static native int ${name}0() throws io.fd.vpp.jvpp.VppInvocationException; + public final int $name() throws io.fd.vpp.jvpp.VppInvocationException { + connection.checkActive(); + LOG.fine("Sending $type event message"); + int result=${name}0(); + if(result<0){ + throw new io.fd.vpp.jvpp.VppInvocationException("${name}", result); + } + return result; + }""") diff --git a/extras/japi/java/jvpp/gen/jvppgen/jvpp_model.py b/extras/japi/java/jvpp/gen/jvppgen/jvpp_model.py new file mode 100755 index 00000000000..299796b908b --- /dev/null +++ b/extras/japi/java/jvpp/gen/jvppgen/jvpp_model.py @@ -0,0 +1,570 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import json +import pprint +from collections import OrderedDict + +BASE_PACKAGE = "io.fd.vpp.jvpp" + + +class ParseException(Exception): + pass + + +class Type(object): + def __init__(self, name, java_name, java_name_fqn, jni_signature, jni_type, jni_accessor, + host_to_net_function, net_to_host_function): + """ + Initializes Type class. + + :param name: name of type as defined in .api file, e.g. u8, u32[] or mac_entry + :param java_name: corresponding java name, e.g. byte, int[] or MacEntry + :param java_name_fqn: fully qualified java name, e.g. io.fd.vpp.jvpp.core.types.MacEntry + :param jni_signature: JNI Type signature, e.g. B, [I or Lio.fd.vpp.jvpp.core.types.MacEntry; + See https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html#type_signatures + :param jni_type: JNI reference type, e.g. jbyte jintArray, jobject + See https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html#reference_types + :param jni_accessor: Java type do by used in Get<type>Field, Set<type>Field and other functions. + See https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#accessing_fields_of_objects + :param host_to_net_function: name of function host to net byte order swap function + :param net_to_host_function: name of function net to host byte order swap function + """ + self.name = name + self.java_name = java_name + + # Java generation specific properties, TODO(VPP-1186): move to Java specific subclass + self.java_name_fqn = java_name_fqn + + # JNI generation specific properties, TODO(VPP-1186): move to JNI specific subclass + self.jni_signature = jni_signature + + # Native type, see: + # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html#primitive_types + # and + # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html#reference_types + self.jni_type = jni_type + + # Java type do by used in Get<type>Field, Set<type>Field and other functions, see: + # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#accessing_fields_of_objects + self.jni_accessor = jni_accessor + + self.host_to_net_function = host_to_net_function + self.net_to_host_function = net_to_host_function + self.is_swap_needed = host_to_net_function and net_to_host_function + + +class SimpleType(Type): + def __init__(self, name, java_name, jni_signature, jni_type, jni_accessor, + host_to_net_function=None, net_to_host_function=None): + super(SimpleType, self).__init__( + name=name, + java_name=java_name, + java_name_fqn=java_name, + jni_signature=jni_signature, + jni_type=jni_type, + jni_accessor=jni_accessor, + host_to_net_function=host_to_net_function, + net_to_host_function=net_to_host_function + ) + self.vpp_name = name + + def get_host_to_net_function(self, host_ref_name, net_ref_name): + return "%s = %s(%s)" % (net_ref_name, self.host_to_net_function, host_ref_name) + + def __str__(self): + return "SimpleType{name:%s, java_name:%s}" % (self.name, self.java_name) + + +# TODO(VPP-1187): add array host to net functions to reduce number of members and simplify JNI generation +class Array(Type): + def __init__(self, base_type): + super(Array, self).__init__( + name=base_type.name + _ARRAY_SUFFIX, + java_name=base_type.java_name + _ARRAY_SUFFIX, + java_name_fqn=base_type.java_name_fqn + _ARRAY_SUFFIX, + jni_signature="[%s" % base_type.jni_signature, + jni_type="%sArray" % base_type.jni_type, + jni_accessor="Object", + host_to_net_function=base_type.host_to_net_function, + net_to_host_function=base_type.net_to_host_function + ) + self.base_type = base_type + + def get_host_to_net_function(self, host_ref_name, net_ref_name): + return self.base_type.get_host_to_net_function(host_ref_name, net_ref_name) + + def __str__(self): + return "Array{name:%s, java_name:%s}" % (self.name, self.java_name) + + +class Enum(Type): + def __init__(self, name, value, constants, definition, plugin_name): + _java_name = _underscore_to_camelcase_upper(name) + + super(Enum, self).__init__( + name=name, + java_name=_java_name, + java_name_fqn="io.fd.vpp.jvpp.%s.types.%s" % (plugin_name, _java_name), + jni_signature="Lio/fd/vpp/jvpp/%s/types/%s;" % (plugin_name, _java_name), + jni_type="jobject", + jni_accessor="Object", + host_to_net_function="_host_to_net_%s" % name, + net_to_host_function="_net_to_host_%s" % name + ) + + self.value = value + self.constants = constants + self.doc = _message_to_javadoc(definition) + self.java_name_lower = _underscore_to_camelcase_lower(name) + self.vpp_name = "%s%s%s" % (_VPP_TYPE_PREFIX, name, _VPP_TYPE_SUFFIX) + # Fully qualified class name used by FindClass function, see: + # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#FindClass + self.jni_name = "io/fd/vpp/jvpp/%s/types/%s" % (plugin_name, _java_name) + + def get_host_to_net_function(self, host_ref_name, net_ref_name): + return "_host_to_net_%s(env, %s, &(%s))" % (self.name, host_ref_name, net_ref_name) + + +class Class(Type): + def __init__(self, name, crc, fields, definition, plugin_name): + _java_name = _underscore_to_camelcase_upper(name) + + super(Class, self).__init__( + name=name, + java_name=_java_name, + java_name_fqn="io.fd.vpp.jvpp.%s.types.%s" % (plugin_name, _java_name), + jni_signature="Lio/fd/vpp/jvpp/%s/types/%s;" % (plugin_name, _java_name), + jni_type="jobject", + jni_accessor="Object", + host_to_net_function="_host_to_net_%s" % name, + net_to_host_function="_net_to_host_%s" % name + ) + + self.crc = crc + self.fields = fields + self.doc = _message_to_javadoc(definition) + self.java_name_lower = _underscore_to_camelcase_lower(name) + self.vpp_name = "%s%s%s" % (_VPP_TYPE_PREFIX, name, _VPP_TYPE_SUFFIX) + # Fully qualified class name used by FindClass function, see: + # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#FindClass + self.jni_name = "io/fd/vpp/jvpp/%s/types/%s" % (plugin_name, _java_name) + + def get_host_to_net_function(self, host_ref_name, net_ref_name): + return "_host_to_net_%s(env, %s, &(%s))" % (self.name, host_ref_name, net_ref_name) + + +class Union(Type): + def __init__(self, name, crc, fields, definition, plugin_name): + _java_name = _underscore_to_camelcase_upper(name) + + super(Union, self).__init__( + name=name, + java_name=_java_name, + java_name_fqn="io.fd.vpp.jvpp.%s.types.%s" % (plugin_name, _java_name), + jni_signature="Lio/fd/vpp/jvpp/%s/types/%s;" % (plugin_name, _java_name), + jni_type="jobject", + jni_accessor="Object", + host_to_net_function="_host_to_net_%s" % name, + net_to_host_function="_net_to_host_%s" % name + ) + + self.crc = crc + self.fields = fields + self.doc = _message_to_javadoc(definition) + self.java_name_lower = _underscore_to_camelcase_lower(name) + self.vpp_name = "%s%s%s" % (_VPP_TYPE_PREFIX, name, _VPP_TYPE_SUFFIX) + # Fully qualified class name used by FindClass function, see: + # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#FindClass + self.jni_name = "io/fd/vpp/jvpp/%s/types/%s" % (plugin_name, _java_name) + + def get_host_to_net_function(self, host_ref_name, net_ref_name): + return "_host_to_net_%s(env, %s, &(%s))" % (self.name, host_ref_name, net_ref_name) + + +class Field(object): + def __init__(self, name, field_type, array_len=None, array_len_field=None): + self.name = name + self.java_name = _underscore_to_camelcase_lower(name) + self.java_name_upper = _underscore_to_camelcase_upper(name) + self.type = field_type + self.array_len = array_len + self.array_len_field = array_len_field + + def __str__(self): + return "Field{name:%s, java_name:%s, type:%s}" % (self.name, self.java_name, self.type) + + +class Message(object): + def __init__(self, name, crc, fields, definition): + self.name = name + self.java_name_upper = _underscore_to_camelcase_upper(name) + self.java_name_lower = _underscore_to_camelcase_lower(name) + self.crc = crc[2:] + self.fields = fields + self.has_fields = fields != [] + self.doc = _message_to_javadoc(definition) + + def __str__(self): + return "Message{name:%s, java_name:%s, crc:%s, fields:%s}" % ( + self.name, self.java_name_upper, self.crc, self.fields) + + +class Event(Message): + def __init__(self, name, crc, fields, definition): + super(Event, self).__init__(name, crc, fields, definition) + + +class Request(Message): + def __init__(self, name, reply, crc, fields, definition): + super(Request, self).__init__(name, crc, fields, definition) + self.reply = reply + self.reply_java = _underscore_to_camelcase_upper(reply) + + def __str__(self): + return "Request{name:%s, reply:%s, crc:%s, fields:%s}" % (self.name, self.reply, self.crc, self.fields) + + +class Reply(Message): + def __init__(self, name, request, crc, fields, definition): + super(Reply, self).__init__(name, crc, fields, definition) + self.request = request + self.request_java = _underscore_to_camelcase_upper(request) + + def __str__(self): + return "Reply{name:%s, request:%s, crc:%s, fields:%s}" % (self.name, self.request, self.crc, self.fields) + + +class Dump(Message): + def __init__(self, name, details, crc, fields, definition): + super(Dump, self).__init__(name, crc, fields, definition) + self.details = details + self.reply_java = _underscore_to_camelcase_upper(details) + + def __str__(self): + return "Dump{name:%s, details:%s, crc:%s, fields:%s}" % (self.name, self.details, self.crc, self.fields) + + +class Details(Message): + def __init__(self, name, dump, crc, fields, definition): + super(Details, self).__init__(name, crc, fields, definition) + self.dump = dump + self.request_java = _underscore_to_camelcase_upper(dump) + + def __str__(self): + return "Details{name:%s, dump:%s, crc:%s, fields:%s}" % (self.name, self.dump, self.crc, self.fields) + + +def is_retval(field): + return field.name == u'retval' + + +def is_array(field): + return field.array_len is not None + + +def is_request(msg): + return hasattr(msg, 'reply') + + +def is_reply(msg): + return hasattr(msg, 'request') + + +def is_dump(msg): + return hasattr(msg, 'details') + + +def is_details(msg): + return hasattr(msg, 'dump') + + +def is_event(msg): + return isinstance(msg, Event) + + +def is_control_ping(msg): + return msg.name == u'control_ping' + + +def is_control_ping_reply(msg): + return msg.name == u'control_ping_reply' + + +class JVppModel(object): + def __init__(self, logger, json_api_files, plugin_name): + self.logger = logger + # TODO(VPP-1188): provide json_file_by_definition map to improve javadoc + self.json_api_files = json_api_files + self.plugin_package = BASE_PACKAGE + "." + plugin_name + self.plugin_name = plugin_name + self.plugin_java_name = _underscore_to_camelcase_upper(plugin_name) + self._load_json_files(json_api_files) + self._parse_services() + self._parse_messages() + self._validate_messages() + + def _load_json_files(self, json_api_files): + types = {} + self._messages = [] + self._services = {} + for file_name in json_api_files: + with open(file_name) as f: + j = json.load(f) + types.update({d[0]: {'type': 'enum', 'data': d} for d in j['enums']}) + types.update({d[0]: {'type': 'type', 'data': d} for d in j['types']}) + types.update({d[0]: {'type': 'union', 'data': d} for d in j['unions']}) + self._messages.extend(j['messages']) + self._services.update(j['services']) + self._parse_types(types) + + def _parse_types(self, types): + self._parse_simple_types() + i = 0 + while True: + unresolved = {} + for name, value in types.items(): + if name in self._types_by_name: + continue + + type = value['type'] + data = value['data'][1:] + try: + if type == 'enum': + type = self._parse_enum(name, data) + elif type == 'union': + type = self._parse_union(name, data) + elif type == 'type': + type = self._parse_type(name, data) + else: + self.logger.warning("Unsupported type %s. Ignoring...", type) + continue + + self._types_by_name[name] = type + self._types_by_name[name + _ARRAY_SUFFIX] = Array(type) + except ParseException as e: + self.logger.debug("Failed to parse %s type in iteration %s: %s.", name, i, e) + unresolved[name] = value + if len(unresolved) == 0: + break + if i > 3: + raise ParseException('Unresolved type definitions {}' + .format(unresolved)) + types = unresolved + i += 1 + + self.types = self._types_by_name.values() + + def _parse_simple_types(self): + # Mapping according to: + # http://docs.oracle.com/javase/7/do+'[]'cs/technotes/guides/jni/spec/types.html + # and + # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#Get_type_Field_routines + # + # Unsigned types are converted to signed java types that have the same size. + # It is the API user responsibility to interpret them correctly. + + self._types_by_name = OrderedDict({ + 'u8': SimpleType('u8', 'byte', 'B', 'jbyte', 'Byte'), + 'i8': SimpleType('i8', 'byte', 'B', 'jbyte', 'Byte'), + 'u16': SimpleType('u16', 'short', 'S', 'jshort', 'Short', + host_to_net_function='clib_host_to_net_u16', + net_to_host_function='clib_net_to_host_u16'), + 'i16': SimpleType('i16', 'short', 'S', 'jshort', 'Short', + host_to_net_function='clib_host_to_net_i16', + net_to_host_function='clib_net_to_host_i16'), + 'u32': SimpleType('u32', 'int', 'I', 'jint', 'Int', + host_to_net_function='clib_host_to_net_u32', + net_to_host_function='clib_net_to_host_u32'), + 'i32': SimpleType('i32', 'int', 'I', 'jint', 'Int', + host_to_net_function='clib_host_to_net_i32', + net_to_host_function='clib_net_to_host_i32'), + 'u64': SimpleType('u64', 'long', 'J', 'jlong', 'Long', + host_to_net_function='clib_host_to_net_u64', + net_to_host_function='clib_net_to_host_u64'), + 'i64': SimpleType('i64', 'long', 'J', 'jlong', 'Long', + host_to_net_function='clib_host_to_net_i64', + net_to_host_function='clib_net_to_host_i64'), + 'f64': SimpleType('f64', 'double', 'D', 'jdouble', 'Double') + }) + + for n, t in self._types_by_name.items(): + self._types_by_name[n + _ARRAY_SUFFIX] = Array(t) + + def _parse_enum(self, name, definition): + self.logger.debug("Parsing enum %s: %s", name, definition) + constants = [] + type_name = None + for item in definition: + if type(item) is dict and 'enumtype' in item: + type_name = item['enumtype'] + continue + constants.append({'name': item[0], 'value': item[1]}) + if not type_name: + raise ParseException("'enumtype' was not defined for %s" % definition) + return Enum(name, Field('value', self._types_by_name[type_name]), constants, definition, self.plugin_name) + + def _parse_union(self, name, definition): + self.logger.debug("Parsing union %s: %s", name, definition) + crc, fields = self._parse_fields(definition) + return Union(name, crc, fields, definition, self.plugin_name) + + def _parse_type(self, name, definition): + self.logger.debug("Parsing type %s: %s", name, definition) + crc, fields = self._parse_fields(definition) + return Class(name, crc, fields, definition, self.plugin_name) + + def _parse_services(self): + self._dumps_by_details = {} + self._requests_by_reply = {} + for name, service in self._services.iteritems(): + if _is_stream(service): + self._dumps_by_details[service['reply']] = name + else: + self._requests_by_reply[service['reply']] = name + + def _parse_messages(self): + # Preserve ordering from JSON file to make debugging easier. + self._messages_by_name = OrderedDict() + for msg in self._messages: + try: + name = msg[0] + definition = msg[1:] + self._messages_by_name[name] = self._parse_message(name, definition) + except ParseException as e: + self.logger.warning("Failed to parse message %s: %s. Skipping message.", name, e) + + def _parse_message(self, name, definition): + self.logger.debug("Parsing message %s: %s", name, definition) + crc, fields = self._parse_fields(definition) + if name in self._services: + service = self._services[name] + reply = service['reply'] + if _is_stream(service): + return Dump(name, reply, crc, filter(_is_request_field, fields), definition) + if reply: + return Request(name, reply, crc, filter(_is_request_field, fields), definition) + else: + return Event(name, crc, filter(_is_request_field, fields), definition) + elif name in self._requests_by_reply: + return Reply(name, self._requests_by_reply[name], crc, filter(_is_reply_field, fields), definition) + elif name in self._dumps_by_details: + return Details(name, self._dumps_by_details[name], crc, filter(_is_reply_field, fields), definition) + else: + # TODO: some messages like combined_counters are not visible in the services. + # Throw exception instead (requires fixing vppagigen). + return Event(name, crc, filter(_is_request_field, fields), definition) + + def _parse_fields(self, definition): + crc = None + fields = [] + for item in definition: + if type(item) == dict and 'crc' in item: + crc = item['crc'] + else: + fields.append(self._parse_field(item, fields)) + if not crc: + raise ParseException("CRC was not defined for %s" % definition) + return crc, fields + + def _parse_field(self, field, fields): + type_name = _extract_type_name(field[0]) + if type_name in self._types_by_name: + if len(field) > 2: + # Array field + array_len_field = None + if len(field) == 4: + for f in fields: + if f.name == field[3]: + array_len_field = f + if not array_len_field: + raise ParseException("Could not find field %s declared as length of array %s", + field[3], field[1]) + return Field(field[1], self._types_by_name[type_name + _ARRAY_SUFFIX], field[2], array_len_field) + else: + return Field(field[1], self._types_by_name[type_name]) + else: + raise ParseException("Unknown field type %s" % field) + + def _validate_messages(self): + """ + In case if message A is known to be reply for message B, and message B was not correctly parsed, + remove message A from the set of all messages. + """ + to_be_removed = [] + messages = self._messages_by_name + for name, msg in messages.iteritems(): + if (is_request(msg) and msg.reply not in messages) \ + or (is_reply(msg) and msg.request not in messages) \ + or (is_dump(msg) and msg.details not in messages) \ + or (is_details(msg) and msg.dump not in messages): + to_be_removed.append(name) + + for name in to_be_removed: + del messages[name] + + self.messages = self._messages_by_name.values() + +_ARRAY_SUFFIX = '[]' + + +def _underscore_to_camelcase_upper(name): + return name.title().replace("_", "") + + +def _underscore_to_camelcase_lower(name): + name = name.title().replace("_", "") + return name[0].lower() + name[1:] + + +def _message_to_javadoc(message_definition): + """ Converts JSON message definition to javadoc """ + formatted_message = pprint.pformat(message_definition, indent=4, width=120, depth=None) + return " * " + formatted_message.replace("\n", "\n * ") + + +def _is_stream(service): + """ + Checks if service represents stream, e.g.: + "ip_address_dump": { + "reply": "ip_address_details", + "stream": true + } + :param service: JSON definition of service + :return: value assigned to "stream" or None + """ + return "stream" in service + + +def _extract_type_name(name): + if name.startswith(_VPP_TYPE_PREFIX) and name.endswith(_VPP_TYPE_SUFFIX): + return name[len(_VPP_TYPE_PREFIX): - len(_VPP_TYPE_SUFFIX)] + return name + +_VPP_TYPE_PREFIX = "vl_api_" + +_VPP_TYPE_SUFFIX = "_t" + + +def _is_request_field(field): + # Skip fields that are hidden to the jvpp user (handled by JNI layer) + return field.name not in {'_vl_msg_id', 'client_index', 'context'} + + +def _is_reply_field(field): + # Skip fields that are hidden to the jvpp user: + # _vl_msg_id is handled at JNI layer, + # Unlike in the request case, context is visible to allow matching replies with requests at Java layer. + return field.name not in {'_vl_msg_id'} diff --git a/extras/japi/java/jvpp/gen/jvppgen/notification_gen.py b/extras/japi/java/jvpp/gen/jvppgen/notification_gen.py new file mode 100644 index 00000000000..69e870ed33d --- /dev/null +++ b/extras/japi/java/jvpp/gen/jvppgen/notification_gen.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from string import Template + +from jvpp_model import is_control_ping, is_control_ping_reply, is_dump, is_request + + +def generate_notifications(work_dir, model, logger): + """ Generates notification registry interface and implementation """ + logger.debug("Generating Notification interfaces and implementation for %s" % model.json_api_files) + messages = filter(_notification_filter, model.messages) + _generate_global_event_callback(work_dir, model, messages) + _generate_event_registry(work_dir, model, messages) + _generate_event_registry_impl(work_dir, model, messages) + _generate_event_registry_provider(work_dir, model) + + +def _notification_filter(msg): + # Generate callbacks for all messages except for dumps and requests (handled by vpp, not client). + # Also skip control ping managed by jvpp registry. + return (not is_control_ping(msg)) and \ + (not is_control_ping_reply(msg)) and \ + (not is_dump(msg)) and \ + (not is_request(msg)) + + +def _generate_event_registry(work_dir, model, messages): + plugin_name = model.plugin_java_name + plugin_package = model.plugin_package + + register_callback_methods = [] + for msg in messages: + name = _callback_name(msg) + fqn_name = _fqn_callback_name(plugin_package, name) + # TODO create NotificationListenerRegistration and return that instead of AutoCloseable to better indicate + # that the registration should be closed + register_callback_methods.append(" java.lang.AutoCloseable register%s(%s callback);" % (name, fqn_name)) + + with open("%s/%sEventRegistry.java" % (work_dir, plugin_name), "w") as f: + f.write(_EVENT_REGISTRY_TEMPLATE.substitute( + plugin_package=plugin_package, + plugin_name=plugin_name, + json_filename=model.json_api_files, + register_callback_methods="\n".join(register_callback_methods) + )) + +_EVENT_REGISTRY_TEMPLATE = Template(""" +package $plugin_package.notification; + +/** + * <p>Registry for notification callbacks defined in ${plugin_name}. + * <br>It was generated by notification_gen.py based on $json_filename. + */ +public interface ${plugin_name}EventRegistry extends io.fd.vpp.jvpp.notification.EventRegistry { + +$register_callback_methods + + @Override + void close(); +} +""") + + +def _generate_event_registry_impl(work_dir, model, messages): + plugin_name = model.plugin_java_name + plugin_package = model.plugin_package + + register_callback_methods = [] + handler_methods = [] + for msg in messages: + notification = msg.java_name_upper + callback = "%sCallback" % notification + register_callback_methods.append(_REGISTER_CALLBACK_IMPL_TEMPLATE.substitute( + plugin_package=plugin_package, + notification=notification, + callback=callback + )) + handler_methods.append(_HANDLER_IMPL_TEMPLATE.substitute( + plugin_package=plugin_package, + notification=notification, + callback=callback + )) + + with open("%s/%sEventRegistryImpl.java" % (work_dir, plugin_name), "w") as f: + f.write(_EVENT_REGISTRY_IMPL_TEMPLATE.substitute( + plugin_package=plugin_package, + plugin_name=plugin_name, + json_filename=model.json_api_files, + register_callback_methods="".join(register_callback_methods), + handler_methods="".join(handler_methods) + )) + +_REGISTER_CALLBACK_IMPL_TEMPLATE = Template(""" + public java.lang.AutoCloseable register$callback(final $plugin_package.callback.$callback callback){ + if(null != registeredCallbacks.putIfAbsent($plugin_package.dto.$notification.class, callback)){ + throw new IllegalArgumentException("Callback for " + $plugin_package.dto.$notification.class + + "notification already registered"); + } + return () -> registeredCallbacks.remove($plugin_package.dto.$notification.class); + } +""") + +_HANDLER_IMPL_TEMPLATE = Template(""" + @Override + public void on$notification( + final $plugin_package.dto.$notification notification) { + if (LOG.isLoggable(java.util.logging.Level.FINE)) { + LOG.fine(String.format("Received $notification event message: %s", notification)); + } + final io.fd.vpp.jvpp.callback.JVppCallback jVppCallback = registeredCallbacks.get($plugin_package.dto.$notification.class); + if (null != jVppCallback) { + (($plugin_package.callback.$callback) registeredCallbacks + .get($plugin_package.dto.$notification.class)) + .on$notification(notification); + } + } +""") + +_EVENT_REGISTRY_IMPL_TEMPLATE = Template(""" +package $plugin_package.notification; + +/** + * <p>Notification registry delegating notification processing to registered callbacks. + * <br>It was generated by notification_gen.py based on $json_filename. + */ +public final class ${plugin_name}EventRegistryImpl implements ${plugin_name}EventRegistry, Global${plugin_name}EventCallback { + + // TODO add a special NotificationCallback interface and only allow those to be registered + private final java.util.concurrent.ConcurrentMap<Class<?>, io.fd.vpp.jvpp.callback.JVppCallback> registeredCallbacks = + new java.util.concurrent.ConcurrentHashMap<>(); + private static java.util.logging.Logger LOG = java.util.logging.Logger.getLogger(${plugin_name}EventRegistryImpl.class.getName()); + + $register_callback_methods + $handler_methods + + @Override + public void close() { + registeredCallbacks.clear(); + } + + @Override + public void onError(io.fd.vpp.jvpp.VppCallbackException ex) { + java.util.logging.Logger LOG = java.util.logging.Logger.getLogger(${plugin_name}EventRegistryImpl.class.getName()); + LOG.log(java.util.logging.Level.WARNING, String.format("Received onError exception: call=%s, context=%d, retval=%d%n", ex.getMethodName(), + ex.getCtxId(), ex.getErrorCode()), ex); + } +} +""") + + +def _generate_global_event_callback(work_dir, model, messages): + plugin_name = model.plugin_java_name + plugin_package = model.plugin_package + + callbacks = "" + callback_list = [] + for msg in messages: + fqn_name = _fqn_callback_name(plugin_package, _callback_name(msg)) + callback_list.append(fqn_name) + + if callback_list: + callbacks = " extends %s" % ", ".join(callback_list) + + with open("%s/Global%sEventCallback.java" % (work_dir, plugin_name), "w") as f: + f.write(_GLOBAL_EVENT_CALLBACK_TEMPLATE.substitute( + plugin_package=plugin_package, + plugin_name=plugin_name, + json_filename=model.json_api_files, + callbacks=callbacks + )) + +_GLOBAL_EVENT_CALLBACK_TEMPLATE = Template(""" +package $plugin_package.notification; + +/** + * <p>Aggregated callback interface for notifications only. + * <br>It was generated by notification_gen.py based on $json_filename. + */ +public interface Global${plugin_name}EventCallback$callbacks { + +} +""") + + +def _generate_event_registry_provider(work_dir, model): + plugin_name = model.plugin_java_name + with open("%s/%sEventRegistryProvider.java" % (work_dir, plugin_name), "w") as f: + f.write(_EVENT_REGISTRY_PROVIDER_TEMPLATE.substitute( + plugin_package=model.plugin_package, + plugin_name=plugin_name, + json_filename=model.json_api_files + )) + +_EVENT_REGISTRY_PROVIDER_TEMPLATE = Template(""" +package $plugin_package.notification; + + /** + * Provides ${plugin_name}EventRegistry. + * <br>The file was generated by notification_gen.py based on $json_filename. + */ +public interface ${plugin_name}EventRegistryProvider extends io.fd.vpp.jvpp.notification.EventRegistryProvider { + + @Override + public ${plugin_name}EventRegistry getEventRegistry(); +} +""") + + +def _callback_name(msg): + return "%sCallback" % msg.java_name_upper + + +def _fqn_callback_name(plugin_package, callback_name): + return "%s.callback.%s" % (plugin_package, callback_name) diff --git a/extras/japi/java/jvpp/gen/jvppgen/types_gen.py b/extras/japi/java/jvpp/gen/jvppgen/types_gen.py new file mode 100755 index 00000000000..3767b530347 --- /dev/null +++ b/extras/japi/java/jvpp/gen/jvppgen/types_gen.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from string import Template + +from jvpp_common_gen import generate_hash_code, generate_equals, generate_to_string, generate_fields +from jvpp_model import Class + + +def generate_types(work_dir, model, logger): + logger.debug("Generating custom types for %s " % model.json_api_files) + + for t in model.types: + if not isinstance(t, Class): + continue + logger.debug("Generating DTO for type %s", t) + type_class_name = t.java_name + fields = t.fields + type_class = _TYPE_TEMPLATE.substitute( + plugin_package=model.plugin_package, + c_type_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + java_type_name=type_class_name, + fields=generate_fields(fields), + hash_code=generate_hash_code(fields), + equals=generate_equals(type_class_name, fields), + to_string=generate_to_string(type_class_name, fields) + ) + with open("%s/%s.java" % (work_dir, type_class_name), "w") as f: + f.write(type_class) + +_TYPE_TEMPLATE = Template(""" +package $plugin_package.types; + +/** + * <p>This class represents $c_type_name type definition. + * <br>It was generated by jvpp_types_gen.py based on $json_filename: + * <pre> +$json_definition + * </pre> + */ +public final class $java_type_name { +$fields +$hash_code +$equals +$to_string +} +""") diff --git a/extras/japi/java/jvpp/gen/jvppgen/unions_gen.py b/extras/japi/java/jvpp/gen/jvppgen/unions_gen.py new file mode 100755 index 00000000000..f67704f2426 --- /dev/null +++ b/extras/japi/java/jvpp/gen/jvppgen/unions_gen.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from string import Template + +from jvpp_common_gen import generate_hash_code, generate_equals, generate_to_string, generate_fields +from jvpp_model import Union + + +def generate_unions(work_dir, model, logger): + logger.debug("Generating unions for %s " % model.json_api_files) + + for t in model.types: + if not isinstance(t, Union): + continue + logger.debug("Generating DTO for union %s", t) + java_union_name = t.java_name + fields = t.fields + type_class = _UNION_TEMPLATE.substitute( + plugin_package=model.plugin_package, + c_type_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + java_union_name=java_union_name, + fields=generate_fields(fields, access_modifier="private"), + constructors=_generate_constructors(java_union_name, fields), + getters=_generate_getters(fields), + hash_code=generate_hash_code(fields), + equals=generate_equals(java_union_name, fields), + to_string=generate_to_string(java_union_name, fields) + ) + with open("%s/%s.java" % (work_dir, java_union_name), "w") as f: + f.write(type_class) + +_UNION_TEMPLATE = Template(""" +package ${plugin_package}.types; + +/** + * <p>This class represents ${c_type_name} union definition. + * <br>It was generated by unions_gen.py based on ${json_filename}: + * <pre> +${json_definition} + * </pre> + */ +public class ${java_union_name} { + private final int _activeMember; +${fields} + private ${java_union_name}() { + // Constructor for JNI usage. All members can be read. + _activeMember = -1; + } +${constructors} +${getters} +${hash_code} +${equals} +${to_string} +} +""") + + +def _generate_constructors(union_name, fields): + return "".join( + _CONSTRUCTOR_TEMPLATE + .substitute(union_name=union_name, + field_type=f.type.java_name_fqn, + field_name=f.java_name, + field_index=i) for i, f in enumerate(fields)) + +_CONSTRUCTOR_TEMPLATE = Template(""" + public ${union_name}(${field_type} ${field_name}) { + this.${field_name} = java.util.Objects.requireNonNull(${field_name}, "${field_name} should not be null"); + _activeMember = $field_index; + }""") + + +def _generate_getters(fields): + return "".join(_GETTER_TEMPLATE.substitute( + type=f.type.java_name_fqn, + getter_name=f.java_name_upper, + field_name=f.java_name + ) for f in fields) + +_GETTER_TEMPLATE = Template(""" + public ${type} get${getter_name}() { + return ${field_name}; + }""") |