From 5b3d95bb3268457f8e9dc264883252b3dcb27141 Mon Sep 17 00:00:00 2001 From: Marek Gradzki Date: Wed, 27 Jun 2018 10:18:18 +0200 Subject: jvpp: add support for unions (VPP-1322) Change-Id: I2456a9b03bcae43793f9ac29eb74eff81269df7b Signed-off-by: Marek Gradzki --- src/vpp-api/java/jvpp/gen/jvpp_gen.py | 2 + .../java/jvpp/gen/jvppgen/jni_common_gen.py | 37 ++++---- .../java/jvpp/gen/jvppgen/jni_type_handlers_gen.py | 80 +++++++++++++++- .../java/jvpp/gen/jvppgen/jvpp_common_gen.py | 8 +- src/vpp-api/java/jvpp/gen/jvppgen/jvpp_model.py | 102 +++++++++++++++------ src/vpp-api/java/jvpp/gen/jvppgen/unions_gen.py | 98 ++++++++++++++++++++ 6 files changed, 277 insertions(+), 50 deletions(-) create mode 100755 src/vpp-api/java/jvpp/gen/jvppgen/unions_gen.py (limited to 'src') diff --git a/src/vpp-api/java/jvpp/gen/jvpp_gen.py b/src/vpp-api/java/jvpp/gen/jvpp_gen.py index 5b8c7fda27c..067a92f86be 100755 --- a/src/vpp-api/java/jvpp/gen/jvpp_gen.py +++ b/src/vpp-api/java/jvpp/gen/jvpp_gen.py @@ -20,6 +20,7 @@ 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 @@ -35,6 +36,7 @@ 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) diff --git a/src/vpp-api/java/jvpp/gen/jvppgen/jni_common_gen.py b/src/vpp-api/java/jvpp/gen/jvppgen/jni_common_gen.py index 742c1736e22..057427dfcfc 100755 --- a/src/vpp-api/java/jvpp/gen/jvppgen/jni_common_gen.py +++ b/src/vpp-api/java/jvpp/gen/jvppgen/jni_common_gen.py @@ -15,7 +15,7 @@ # from string import Template -from jvpp_model import is_array, is_retval, Class, Enum +from jvpp_model import is_array, is_retval, Class, Enum, Union def generate_j2c_identifiers(element, class_ref_name, object_ref_name): @@ -42,18 +42,21 @@ _REQUEST_FIELD_IDENTIFIER_TEMPLATE = Template(""" def generate_j2c_swap(element, struct_ref_name): initialization = [] for field in element.fields: - if is_array(field): - initialization.append(_generate_j2c_array_swap(field, struct_ref_name)) - else: - initialization.append(_generate_j2c_scalar_swap(field, struct_ref_name)) - + 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) or isinstance(base_type, Enum): + if isinstance(base_type, Class) or isinstance(base_type, Enum) or isinstance(base_type, 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) @@ -181,8 +184,8 @@ def generate_c2j_swap(element, object_ref_name, struct_ref_name): 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): - return _generate_c2j_class_array_swap(msg_java_name, field, object_ref_name, struct_ref_name) + if isinstance(base_type, Class) or isinstance(base_type, 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: @@ -191,9 +194,9 @@ def _generate_c2j_array_swap(msg_java_name, field, object_ref_name, struct_ref_n return _generate_c2j_primitive_type_array_no_swap(msg_java_name, field, object_ref_name, struct_ref_name) -def _generate_c2j_class_array_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_CLASS_ARRAY_SWAP_TEMPLATE.substitute( + 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, @@ -205,7 +208,7 @@ def _generate_c2j_class_array_swap(msg_java_name, field, object_ref_name, struct c_name=field.name ) -_C2J_CLASS_ARRAY_SWAP_TEMPLATE = Template(""" +_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}"); @@ -329,8 +332,8 @@ def _generate_c2j_scalar_swap(msg_java_name, field, object_ref_name, struct_ref_ field_type = field.type if field_type.is_swap_needed: # TODO(VPP-1186): move the logic to JNI generators - if isinstance(field_type, Class): - return _generate_c2j_class_swap(msg_java_name, field, object_ref_name, struct_ref_name) + if isinstance(field_type, Class) or isinstance(field_type, 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: @@ -339,9 +342,9 @@ def _generate_c2j_scalar_swap(msg_java_name, field, object_ref_name, struct_ref_ return _generate_c2j_primitive_type_no_swap(msg_java_name, field, object_ref_name, struct_ref_name) -def _generate_c2j_class_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_CLASS_SWAP_TEMPLATE.substitute( + return _C2J_OBJECT_SWAP_TEMPLATE.substitute( java_name=field.java_name, class_ref_name=msg_java_name, jni_signature=field_type.jni_signature, @@ -352,7 +355,7 @@ def _generate_c2j_class_swap(msg_java_name, field, object_ref_name, struct_ref_n net_to_host_function=field_type.net_to_host_function, c_name=field.name) -_C2J_CLASS_SWAP_TEMPLATE = Template(""" +_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, "", "()V"); diff --git a/src/vpp-api/java/jvpp/gen/jvppgen/jni_type_handlers_gen.py b/src/vpp-api/java/jvpp/gen/jvppgen/jni_type_handlers_gen.py index 31642e53a73..48566f6234b 100755 --- a/src/vpp-api/java/jvpp/gen/jvppgen/jni_type_handlers_gen.py +++ b/src/vpp-api/java/jvpp/gen/jvppgen/jni_type_handlers_gen.py @@ -15,8 +15,8 @@ # from string import Template -from jni_common_gen import generate_j2c_swap, generate_j2c_identifiers, generate_c2j_swap -from jvpp_model import Class, Enum +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): @@ -32,6 +32,8 @@ def generate_type_handlers(model, logger): _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) @@ -148,4 +150,76 @@ def _generate_scalar_net_to_host_swap(field): if field_type.is_swap_needed: return "%s((%s) _net);" % (field_type.net_to_host_function, field_type.name) else: - return "_net" \ No newline at end of file + 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/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_common_gen.py b/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_common_gen.py index 73615ca7178..83226ea78ac 100755 --- a/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_common_gen.py +++ b/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_common_gen.py @@ -17,10 +17,12 @@ from string import Template from jvpp_model import is_array -def generate_fields(fields): - return "\n".join(_FIELD_TEMPLATE.substitute(type=f.type.java_name_fqn, name=f.java_name) for f in fields) +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(""" public $type $name;""") +_FIELD_TEMPLATE = Template(""" ${access_modifier} ${type} ${name};""") def generate_hash_code(fields): diff --git a/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_model.py b/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_model.py index 024e8ffd7d8..299796b908b 100755 --- a/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_model.py +++ b/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_model.py @@ -166,10 +166,39 @@ class Class(Type): 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 @@ -283,25 +312,60 @@ class JVppModel(object): self.plugin_name = plugin_name self.plugin_java_name = _underscore_to_camelcase_upper(plugin_name) self._load_json_files(json_api_files) - self._parse_simple_types() - self._parse_enums() - self._parse_types() self._parse_services() self._parse_messages() self._validate_messages() def _load_json_files(self, json_api_files): - self._enums = [] - self._types = [] + types = {} self._messages = [] self._services = {} for file_name in json_api_files: with open(file_name) as f: j = json.load(f) - self._enums.extend(j['enums']) - self._types.extend(j['types']) + 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: @@ -339,14 +403,6 @@ class JVppModel(object): for n, t in self._types_by_name.items(): self._types_by_name[n + _ARRAY_SUFFIX] = Array(t) - def _parse_enums(self): - for json_type in self._enums: - name = json_type[0] - definition = json_type[1:] - _type = self._parse_enum(name, definition) - self._types_by_name[name] = _type - self._types_by_name[name + _ARRAY_SUFFIX] = Array(_type) - def _parse_enum(self, name, definition): self.logger.debug("Parsing enum %s: %s", name, definition) constants = [] @@ -360,18 +416,10 @@ class JVppModel(object): 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_types(self): - for json_type in self._types: - try: - name = json_type[0] - definition = json_type[1:] - _type = self._parse_type(name, definition) - self._types_by_name[name] = _type - self._types_by_name[name + _ARRAY_SUFFIX] = Array(_type) - except ParseException as e: - self.logger.warning("Failed to parse %s type: %s. Skipping type definition.", name, e) - - self.types = self._types_by_name.values() + 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) diff --git a/src/vpp-api/java/jvpp/gen/jvppgen/unions_gen.py b/src/vpp-api/java/jvpp/gen/jvppgen/unions_gen.py new file mode 100755 index 00000000000..f67704f2426 --- /dev/null +++ b/src/vpp-api/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; + +/** + *

This class represents ${c_type_name} union definition. + *
It was generated by unions_gen.py based on ${json_filename}: + *

+${json_definition}
+ * 
+ */ +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}; + }""") -- cgit 1.2.3-korg