jvpp: add support for unions (VPP-1322) 35/13235/2
authorMarek Gradzki <mgradzki@cisco.com>
Wed, 27 Jun 2018 08:18:18 +0000 (10:18 +0200)
committerDamjan Marion <dmarion@me.com>
Wed, 27 Jun 2018 12:59:31 +0000 (12:59 +0000)
Change-Id: I2456a9b03bcae43793f9ac29eb74eff81269df7b
Signed-off-by: Marek Gradzki <mgradzki@cisco.com>
src/vpp-api/java/jvpp/gen/jvpp_gen.py
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_common_gen.py
src/vpp-api/java/jvpp/gen/jvppgen/jvpp_model.py
src/vpp-api/java/jvpp/gen/jvppgen/unions_gen.py [new file with mode: 0755]

index 5b8c7fd..067a92f 100755 (executable)
@@ -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)
index 742c173..057427d 100755 (executable)
@@ -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, "<init>", "()V");
index 31642e5..48566f6 100755 (executable)
@@ -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
+}""")
index 73615ca..83226ea 100755 (executable)
@@ -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):
index 024e8ff..299796b 100755 (executable)
@@ -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 (executable)
index 0000000..f67704f
--- /dev/null
@@ -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};
+    }""")