jvpp: add support for enums (VPP-1153) 54/13154/3
authorMarek Gradzki <mgradzki@cisco.com>
Wed, 20 Jun 2018 12:49:39 +0000 (14:49 +0200)
committerOle Trøan <otroan@employees.org>
Fri, 22 Jun 2018 09:27:47 +0000 (09:27 +0000)
Change-Id: I2a1b946a71419e1fb3c5d70567c54a6a7d841f10
Signed-off-by: Marek Gradzki <mgradzki@cisco.com>
src/vpp-api/java/jvpp/gen/jvpp_gen.py
src/vpp-api/java/jvpp/gen/jvppgen/enums_gen.py [new file with mode: 0755]
src/vpp-api/java/jvpp/gen/jvppgen/jni_common_gen.py
src/vpp-api/java/jvpp/gen/jvppgen/jni_type_handlers_gen.py
src/vpp-api/java/jvpp/gen/jvppgen/jvpp_model.py

index a58c580..5b8c7fd 100755 (executable)
@@ -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 (executable)
index 0000000..8ba9655
--- /dev/null
@@ -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)""")
index 3b9969c..742c173 100755 (executable)
@@ -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(
index c32acc1..31642e5 100755 (executable)
@@ -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:
index 8b60c0d..024e8ff 100755 (executable)
@@ -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]