Skip to content

Commit

Permalink
Move CHIPReadCallbacks.h in java codegen from zap to jinja (#25865)
Browse files Browse the repository at this point in the history
* Start a first codegen for the read callbacks header

* Start fixing includes

* Fix unit tests and builds

* Fix file naming

* Fix an include path to point to compile time codegen

* Fix another include path

* Remove unused file

---------

Co-authored-by: Andrei Litvin <[email protected]>
  • Loading branch information
2 people authored and pull[bot] committed Mar 27, 2024
1 parent 444867d commit 2053336
Show file tree
Hide file tree
Showing 33 changed files with 2,177 additions and 15,103 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
*
* Copyright (c) 2023 Project CHIP Authors
*
* 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.
*/
#include <jni/CHIPCallbackTypes.h>

#include <controller/java/AndroidCallbacks.h>
#include <jni.h>
#include <lib/support/ErrorStr.h>
#include <lib/support/JniReferences.h>
#include <zap-generated/CHIPClientCallbacks.h>

{% for type in globalTypes -%}
class CHIP{{type.name}}AttributeCallback : public chip::Callback::Callback<{{type.name}}AttributeCallback>
{
public:
CHIP{{type.name}}AttributeCallback(jobject javaCallback, bool keepAlive = false);

~CHIP{{type.name}}AttributeCallback();

static void maybeDestroy(CHIP{{type.name}}AttributeCallback * callback) {
if (!callback->keepAlive) {
callback->Cancel();
chip::Platform::Delete<CHIP{{type.name}}AttributeCallback>(callback);
}
}

static void CallbackFn(void * context, {{type.cpp_type}} value);
static void OnSubscriptionEstablished(void * context, chip::SubscriptionId subscriptionId) {
CHIP_ERROR err = chip::JniReferences::GetInstance().CallSubscriptionEstablished(reinterpret_cast<CHIP{{type.name}}AttributeCallback *>(context)->javaCallbackRef, subscriptionId);
VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Zcl, "Error calling onSubscriptionEstablished: %s", ErrorStr(err)));
};

private:
jobject javaCallbackRef;
bool keepAlive;
};
{% endfor %}

{% for cluster in clientClusters | sort(attribute='code') %}
{% set typeLookup = idl | createLookupContext(cluster) %}
{%- for attr in cluster.attributes | rejectattr('definition', 'is_using_global_callback', typeLookup) %}
class CHIP{{cluster.name}}{{attr.definition.name | capitalcase}}AttributeCallback : public chip::Callback::Callback<CHIP{{cluster.name}}Cluster{{attr.definition.name | capitalcase}}AttributeCallbackType>
{
public:
CHIP{{cluster.name}}{{attr.definition.name | capitalcase}}AttributeCallback(jobject javaCallback, bool keepAlive = false);

~CHIP{{cluster.name}}{{attr.definition.name | capitalcase}}AttributeCallback();

static void maybeDestroy(CHIP{{cluster.name}}{{attr.definition.name | capitalcase}}AttributeCallback * callback) {
if (!callback->keepAlive) {
callback->Cancel();
chip::Platform::Delete<CHIP{{cluster.name}}{{attr.definition.name | capitalcase}}AttributeCallback>(callback);
}
}

static void CallbackFn(void * context, {{attr.definition | decodableJniType(typeLookup)}} {%if attr.definition.is_list%}list{%else%}value{%endif%});
static void OnSubscriptionEstablished(void * context, chip::SubscriptionId subscriptionId) {
CHIP_ERROR err = chip::JniReferences::GetInstance().CallSubscriptionEstablished(reinterpret_cast<CHIP{{cluster.name}}{{attr.definition.name | capitalcase}}AttributeCallback *>(context)->javaCallbackRef, subscriptionId);
VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Zcl, "Error calling onSubscriptionEstablished: %s", ErrorStr(err)));
};

private:
jobject javaCallbackRef;
bool keepAlive;
};

{% endfor %}
{% endfor %}
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@
{% endmacro -%}

#include <jni/CHIPCallbackTypes.h>
#include <jni/CHIPReadCallbacks.h>
#include <controller/java/zap-generated/CHIPInvokeCallbacks.h>
#include <controller/java/zap-generated/CHIPReadCallbacks.h>

#include <app-common/zap-generated/cluster-objects.h>
#include <zap-generated/CHIPClientCallbacks.h>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#}
#include <controller/java/zap-generated/CHIPReadCallbacks.h>
#include <jni/CHIPReadCallbacks.h>

#include <app-common/zap-generated/cluster-objects.h>
#include <zap-generated/CHIPClusters.h>
Expand Down
232 changes: 205 additions & 27 deletions scripts/py_matter_idl/matter_idl/generators/java/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import dataclasses
import enum
import logging
import os
Expand All @@ -26,19 +27,37 @@
from stringcase import capitalcase


def FieldToGlobalName(field: Field, context: TypeLookupContext) -> Union[str, None]:
"""Global names are used for generic callbacks shared across
all clusters (e.g. for bool/float/uint32 and similar)
"""
if field.is_list:
return None # lists are always specific per cluster
@dataclasses.dataclass
class GenerateTarget:
template: str
output_name: str

if FieldQuality.NULLABLE & field.qualities:
return None

if FieldQuality.OPTIONAL & field.qualities:
return None
@dataclasses.dataclass
class GlobalType:
name: str # java name
cpp_type: str # underlying type


# types that java should see globally
_GLOBAL_TYPES = [
GlobalType("Boolean", "bool"),
GlobalType("CharString", "const chip::CharSpan"),
GlobalType("Double", "double"),
GlobalType("Float", "float"),
GlobalType("Int8s", "int8_t"),
GlobalType("Int8u", "uint8_t"),
GlobalType("Int16s", "int16_t"),
GlobalType("Int16u", "uint16_t"),
GlobalType("Int32s", "int32_t"),
GlobalType("Int32u", "uint32_t"),
GlobalType("Int64s", "int64_t"),
GlobalType("Int64u", "uint64_t"),
GlobalType("OctetString", "const chip::ByteSpan"),
]


def _UnderlyingType(field: Field, context: TypeLookupContext) -> Union[str, None]:
actual = ParseDataType(field.data_type, context)
if type(actual) == IdlEnumType:
actual = actual.base_type
Expand Down Expand Up @@ -68,6 +87,99 @@ def FieldToGlobalName(field: Field, context: TypeLookupContext) -> Union[str, No
return None


def FieldToGlobalName(field: Field, context: TypeLookupContext) -> Union[str, None]:
"""Global names are used for generic callbacks shared across
all clusters (e.g. for bool/float/uint32 and similar)
"""
if field.is_list:
return None # lists are always specific per cluster

if FieldQuality.NULLABLE & field.qualities:
return None

if FieldQuality.OPTIONAL & field.qualities:
return None

return _UnderlyingType(field, context)


# Based on atomicType in ZAP:
# src-electron/generator/matter/app/zap-templates/common/override.js
_KNOWN_DECODABLE_TYPES = {
'action_id': 'chip::ActionId',
'attrib_id': 'chip::AttributeId',
'cluster_id': 'chip::ClusterId',
'command_id': 'chip::CommandId',
'data_ver': 'chip::DataVersion',
'devtype_id': 'chip::DeviceTypeId',
'endpoint_no': 'chip::EndpointId',
'eui64': 'chip::NodeId',
'event_id': 'chip::EventId',
'event_no': 'chip::EventNumber',
'fabric_id': 'chip::FabricId',
'fabric_idx': 'chip::FabricIndex',
'fabric_idx': 'chip::FabricIndex',
'field_id': 'chip::FieldId',
'group_id': 'chip::GroupId',
'node_id': 'chip::NodeId',
'percent': 'chip::Percent',
'percent100ths': 'chip::Percent100ths',
'transaction_id': 'chip::TransactionId',
'vendor_id': 'chip::VendorId',

# non-named enums
'enum8': 'uint8_t',
'enum16': 'uint16_t',
'enum32': 'uint32_t',
'enum64': 'uint64_t',
}


def _CppType(field: Field, context: TypeLookupContext) -> Union[str, None]:
if field.data_type.name.lower() in _KNOWN_DECODABLE_TYPES:
return _KNOWN_DECODABLE_TYPES[field.data_type.name.lower()]

actual = ParseDataType(field.data_type, context)
if isinstance(actual, BasicString):
if actual.is_binary:
return 'chip::ByteSpan'
else:
return 'chip::CharSpan'
elif isinstance(actual, BasicInteger):
if actual.is_signed:
return "int{}_t".format(actual.power_of_two_bits)
else:
return "uint{}_t".format(actual.power_of_two_bits)
elif isinstance(actual, FundamentalType):
if actual == FundamentalType.BOOL:
return 'bool'
elif actual == FundamentalType.FLOAT:
return 'float'
elif actual == FundamentalType.DOUBLE:
return 'double'
else:
logging.warn('Unknown fundamental type: %r' % actual)
elif isinstance(actual, IdlType):
return f"chip::app::Clusters::{context.cluster.name}::Structs::{field.data_type.name}::DecodableType"
elif isinstance(actual, IdlBitmapType):
return f"chip::BitMask<chip::app::Clusters::{context.cluster.name}::{field.data_type.name}>"

# Handles IdlEnumType
return f"chip::app::Clusters::{context.cluster.name}::{field.data_type.name}"


def DecodableJniType(field: Field, context: TypeLookupContext) -> str:
actual = _CppType(field, context)

if field.is_list:
return f"const chip::app::DataModel::DecodableList<{actual}> &"

if field.is_nullable:
return f"const chip::app::DataModel::Nullable<{actual}> &"

return actual


def GlobalNameToJavaName(name: str) -> str:
if name in {'Int8u', 'Int8s', 'Int16u', 'Int16s'}:
return 'Integer'
Expand Down Expand Up @@ -143,6 +255,51 @@ def attributesWithSupportedCallback(attrs, context: TypeLookupContext):
yield attr


def _IsUsingGlobalCallback(field: Field, context: TypeLookupContext):
"""Test to determine if the data type of a field can use one of
the global callbacks (i.e. it is a basic double/integer/bool etc.)
"""
if field.is_list:
return False

if field.is_nullable:
return False

return field.data_type.name in {
"boolean",
"single",
"double",
"int8s",
"int8u",
"int16s",
"int16u",
"int24s",
"int24u",
"int32s",
"int32u",
"int40s",
"int40u",
"int48s",
"int48u",
"int56s",
"int56u",
"int64s",
"int64u",
"enum8",
"enum16",
"enum32",
"enum64",
"bitmap8",
"bitmap16",
"bitmap32",
"bitmap64",
"char_string",
"long_char_string",
"octet_string",
"long_octet_string",
}


def NamedFilter(choices: List, name: str):
for choice in choices:
if choice.name == name:
Expand Down Expand Up @@ -419,8 +576,10 @@ def __init__(self, storage: GeneratorStorage, idl: Idl, **kargs):
self.jinja_env.filters['asEncodable'] = EncodableValueFrom
self.jinja_env.filters['createLookupContext'] = CreateLookupContext
self.jinja_env.filters['canGenerateSubscribe'] = CanGenerateSubscribe
self.jinja_env.filters['decodableJniType'] = DecodableJniType

self.jinja_env.tests['is_response_struct'] = IsResponseStruct
self.jinja_env.tests['is_using_global_callback'] = _IsUsingGlobalCallback


class JavaJNIGenerator(__JavaCodeGenerator):
Expand All @@ -434,6 +593,31 @@ def internal_render_all(self):
Renders .CPP files required for JNI support.
"""

large_targets = [
GenerateTarget(template="CHIPCallbackTypes.jinja",
output_name="jni/CHIPCallbackTypes.h"),
GenerateTarget(template="CHIPReadCallbacks_h.jinja",
output_name="jni/CHIPReadCallbacks.h")
]

for target in large_targets:
self.internal_render_one_output(
template_path=target.template,
output_file_name=target.output_name,
vars={
'idl': self.idl,
'clientClusters': [c for c in self.idl.clusters if c.side == ClusterSide.CLIENT],
'globalTypes': _GLOBAL_TYPES,
}
)

cluster_targets = [
GenerateTarget(template="ChipClustersRead.jinja",
output_name="jni/{cluster_name}Client-ReadImpl.cpp"),
GenerateTarget(template="ChipClustersCpp.jinja",
output_name="jni/{cluster_name}Client-InvokeSubscribeImpl.cpp"),
]

self.internal_render_one_output(
template_path="CHIPCallbackTypes.jinja",
output_file_name="jni/CHIPCallbackTypes.h",
Expand All @@ -449,23 +633,17 @@ def internal_render_all(self):
if cluster.side != ClusterSide.CLIENT:
continue

self.internal_render_one_output(
template_path="ChipClustersRead.jinja",
output_file_name="jni/%sClient-ReadImpl.cpp" % cluster.name,
vars={
'cluster': cluster,
'typeLookup': TypeLookupContext(self.idl, cluster),
}
)

self.internal_render_one_output(
template_path="ChipClustersCpp.jinja",
output_file_name="jni/%sClient-InvokeSubscribeImpl.cpp" % cluster.name,
vars={
'cluster': cluster,
'typeLookup': TypeLookupContext(self.idl, cluster),
}
)
for target in cluster_targets:
self.internal_render_one_output(
template_path=target.template,
output_file_name=target.output_name.format(
cluster_name=cluster.name),
vars={
'cluster': cluster,
'typeLookup': TypeLookupContext(self.idl, cluster),
'globalTypes': _GLOBAL_TYPES,
}
)


class JavaClassGenerator(__JavaCodeGenerator):
Expand Down
Loading

0 comments on commit 2053336

Please sign in to comment.