From 3e2d57d8773e1d03577048f497dc7ed567fd9344 Mon Sep 17 00:00:00 2001 From: Marek Gradzki Date: Wed, 20 Jun 2018 14:49:39 +0200 Subject: jvpp: add support for enums (VPP-1153) Change-Id: I2a1b946a71419e1fb3c5d70567c54a6a7d841f10 Signed-off-by: Marek Gradzki --- src/vpp-api/java/jvpp/gen/jvpp_gen.py | 2 + src/vpp-api/java/jvpp/gen/jvppgen/enums_gen.py | 74 ++++++++++++++++++++++ .../java/jvpp/gen/jvppgen/jni_common_gen.py | 66 ++++++++++++++++++- .../java/jvpp/gen/jvppgen/jni_type_handlers_gen.py | 52 ++++++++++++++- src/vpp-api/java/jvpp/gen/jvppgen/jvpp_model.py | 54 +++++++++++++++- 5 files changed, 244 insertions(+), 4 deletions(-) create mode 100755 src/vpp-api/java/jvpp/gen/jvppgen/enums_gen.py (limited to 'src/vpp-api/java/jvpp') diff --git a/src/vpp-api/java/jvpp/gen/jvpp_gen.py b/src/vpp-api/java/jvpp/gen/jvpp_gen.py index a58c5809a6d..5b8c7fda27c 100755 --- a/src/vpp-api/java/jvpp/gen/jvpp_gen.py +++ b/src/vpp-api/java/jvpp/gen/jvpp_gen.py @@ -19,6 +19,7 @@ import os import sys from jvppgen.types_gen import generate_types +from jvppgen.enums_gen import generate_enums 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 @@ -33,6 +34,7 @@ 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_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/enums_gen.py b/src/vpp-api/java/jvpp/gen/jvppgen/enums_gen.py new file mode 100755 index 00000000000..8ba96558428 --- /dev/null +++ b/src/vpp-api/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; + +/** + *

This class represents $c_type_name enum definition. + *
It was generated by enums_gen.py based on $json_filename: + *

+$json_definition
+ * 
+ */ +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/src/vpp-api/java/jvpp/gen/jvppgen/jni_common_gen.py b/src/vpp-api/java/jvpp/gen/jvppgen/jni_common_gen.py index 3b9969c36e0..742c1736e22 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 +from jvpp_model import is_array, is_retval, Class, Enum def generate_j2c_identifiers(element, class_ref_name, object_ref_name): @@ -53,7 +53,7 @@ def generate_j2c_swap(element, 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): + if isinstance(base_type, Class) or isinstance(base_type, Enum): 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) @@ -183,6 +183,8 @@ def _generate_c2j_array_swap(msg_java_name, field, object_ref_name, struct_ref_n 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) + 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: @@ -222,6 +224,40 @@ _C2J_CLASS_ARRAY_SWAP_TEMPLATE = Template(""" """) +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( @@ -295,6 +331,8 @@ def _generate_c2j_scalar_swap(msg_java_name, field, object_ref_name, struct_ref_ # 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) + 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: @@ -325,6 +363,30 @@ _C2J_CLASS_SWAP_TEMPLATE = Template(""" """) +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( 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 c32acc1911c..31642e53a73 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 @@ -16,7 +16,7 @@ from string import Template from jni_common_gen import generate_j2c_swap, generate_j2c_identifiers, generate_c2j_swap -from jvpp_model import Class +from jvpp_model import Class, Enum def generate_type_handlers(model, logger): @@ -30,6 +30,8 @@ def generate_type_handlers(model, logger): #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) else: logger.debug("Skipping custom JNI type handler generation for %s", t) @@ -85,6 +87,54 @@ $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: 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 8b60c0dc4f3..024e8ffd7d8 100755 --- a/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_model.py +++ b/src/vpp-api/java/jvpp/gen/jvppgen/jvpp_model.py @@ -110,6 +110,34 @@ class Array(Type): 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) @@ -255,6 +283,8 @@ 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() @@ -273,7 +303,7 @@ class JVppModel(object): self._messages.extend(j['messages']) self._services.update(j['services']) - def _parse_types(self): + def _parse_simple_types(self): # Mapping according to: # http://docs.oracle.com/javase/7/do+'[]'cs/technotes/guides/jni/spec/types.html # and @@ -309,6 +339,28 @@ 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 = [] + 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_types(self): for json_type in self._types: try: name = json_type[0] -- cgit 1.2.3-korg