[jnigen] Generate JValue wrapper classes in pure Dart bindings (https://github.com/dart-lang/jnigen/issues/212)
* closes https://github.com/dart-lang/jnigen/issues/221
diff --git a/pkgs/jni/example/integration_test/on_device_jni_test.dart b/pkgs/jni/example/integration_test/on_device_jni_test.dart
index 2bc0d9a..3b547ad 100644
--- a/pkgs/jni/example/integration_test/on_device_jni_test.dart
+++ b/pkgs/jni/example/integration_test/on_device_jni_test.dart
@@ -39,7 +39,7 @@
testWidgets("call a static method using JniClass APIs", (t) async {
final integerClass = JniClass.fromRef(Jni.findClass("java/lang/Integer"));
final result = integerClass.callStaticMethodByName<JString>(
- "toHexString", "(I)Ljava/lang/String;", [31]);
+ "toHexString", "(I)Ljava/lang/String;", [JValueInt(31)]);
final resultString = result.toDartString();
@@ -58,10 +58,9 @@
final nextIntMethod = random.getMethodID("nextInt", "(I)I");
for (int i = 0; i < 100; i++) {
- int r = random.callMethod<int>(nextIntMethod, [256 * 256]);
+ int r = random.callMethod<int>(nextIntMethod, [JValueInt(256 * 256)]);
int bits = 0;
- final jbc =
- longClass.callStaticMethod<int>(bitCountMethod, [JValueLong(r)]);
+ final jbc = longClass.callStaticMethod<int>(bitCountMethod, [r]);
while (r != 0) {
bits += r % 2;
r = (r / 2).floor();
@@ -74,8 +73,8 @@
// Actually it's not even required to get a reference to class
testWidgets("invoke_", (t) async {
- final m = Jni.invokeStaticMethod<int>("java/lang/Long", "min", "(JJ)J",
- [JValueLong(1234), JValueLong(1324)], JniCallType.longType);
+ final m = Jni.invokeStaticMethod<int>(
+ "java/lang/Long", "min", "(JJ)J", [1234, 1324], JniCallType.longType);
expect(m, equals(1234));
});
@@ -98,7 +97,7 @@
final longClass = Jni.findJniClass("java/lang/Long");
const n = 1223334444;
final strFromJava = longClass.callStaticMethodByName<String>(
- "toOctalString", "(J)Ljava/lang/String;", [JValueLong(n)]);
+ "toOctalString", "(J)Ljava/lang/String;", [n]);
expect(strFromJava, equals(n.toRadixString(8)));
longClass.delete();
});
@@ -110,8 +109,9 @@
});
testWidgets("use() method", (t) async {
- final randomInt = Jni.newInstance("java/util/Random", "()V", [])
- .use((random) => random.callMethodByName<int>("nextInt", "(I)I", [15]));
+ final randomInt = Jni.newInstance("java/util/Random", "()V", []).use(
+ (random) =>
+ random.callMethodByName<int>("nextInt", "(I)I", [JValueInt(15)]));
expect(randomInt, lessThan(15));
});
diff --git a/pkgs/jni/lib/src/jni.dart b/pkgs/jni/lib/src/jni.dart
index b165f8e..4d5a2ab 100644
--- a/pkgs/jni/lib/src/jni.dart
+++ b/pkgs/jni/lib/src/jni.dart
@@ -214,9 +214,9 @@
/// Converts passed arguments to JValue array.
///
- /// int, bool, double and JObject types are converted out of the box.
- /// Wrap values in types such as [JValueLong] to convert to other primitive
- /// types such as `long`, `short` and `char`.
+ /// long, bool, double and JObject types are converted out of the box.
+ /// Wrap values in types such as [JValueInt] to convert to other primitive
+ /// types such as `int`, `short` and `char`.
static Pointer<JValue> jvalues(List<dynamic> args,
{Allocator allocator = calloc}) {
return toJValues(args, allocator: allocator);
diff --git a/pkgs/jni/lib/src/jvalues.dart b/pkgs/jni/lib/src/jvalues.dart
index 8718bb1..ce0cac6 100644
--- a/pkgs/jni/lib/src/jvalues.dart
+++ b/pkgs/jni/lib/src/jvalues.dart
@@ -17,7 +17,7 @@
switch (arg.runtimeType) {
case int:
- pos.ref.i = arg;
+ pos.ref.j = arg;
break;
case bool:
pos.ref.z = arg ? 1 : 0;
@@ -32,8 +32,8 @@
case JValueFloat:
pos.ref.f = (arg as JValueFloat).value;
break;
- case JValueLong:
- pos.ref.j = (arg as JValueLong).value;
+ case JValueInt:
+ pos.ref.i = (arg as JValueInt).value;
break;
case JValueShort:
pos.ref.s = (arg as JValueShort).value;
@@ -66,10 +66,10 @@
}
/// Use this class as wrapper to convert an integer
-/// to Java `long` in jvalues method.
-class JValueLong {
+/// to Java `int` in jvalues method.
+class JValueInt {
int value;
- JValueLong(this.value);
+ JValueInt(this.value);
}
/// Use this class as wrapper to convert an integer
diff --git a/pkgs/jni/test/exception_test.dart b/pkgs/jni/test/exception_test.dart
index c954678..c277b01 100644
--- a/pkgs/jni/test/exception_test.dart
+++ b/pkgs/jni/test/exception_test.dart
@@ -41,7 +41,7 @@
test("Use after free throws exception", () {
final r = Jni.newInstance("java/util/Random", "()V", []);
r.delete();
- expect(() => r.callMethodByName<int>("nextInt", "(I)I", [256]),
+ expect(() => r.callMethodByName<int>("nextInt", "(I)I", [JValueInt(256)]),
throwsA(isA<UseAfterFreeException>()));
});
@@ -57,13 +57,13 @@
final r = Jni.newInstance("java/util/Random", "()V", []);
expect(
() => r.callMethodByName<int>(
- "nextInt", "(I)I", [256], JniCallType.doubleType),
+ "nextInt", "(I)I", [JValueInt(256)], JniCallType.doubleType),
throwsA(isA<InvalidCallTypeException>()));
});
test("An exception in JNI throws JniException in Dart", () {
final r = Jni.newInstance("java/util/Random", "()V", []);
- expect(() => r.callMethodByName<int>("nextInt", "(I)I", [-1]),
+ expect(() => r.callMethodByName<int>("nextInt", "(I)I", [JValueInt(-1)]),
throwsA(isA<JniException>()));
});
}
diff --git a/pkgs/jni/test/jni_test.dart b/pkgs/jni/test/jni_test.dart
index a40ba81..19bae21 100644
--- a/pkgs/jni/test/jni_test.dart
+++ b/pkgs/jni/test/jni_test.dart
@@ -49,26 +49,26 @@
});
test(
- 'Manually lookup & call Long.toHexString',
+ 'Manually lookup & call Integer.toHexString',
() => using((arena) {
// Method names on JniEnv* from C JNI API are capitalized
// like in original, while other extension methods
// follow Dart naming conventions.
- final longClass =
- env.FindClass("java/lang/Long".toNativeChars(arena));
+ final integerClass =
+ env.FindClass("java/lang/Integer".toNativeChars(arena));
// Refer JNI spec on how to construct method signatures
// Passing wrong signature leads to a segfault
final hexMethod = env.GetStaticMethodID(
- longClass,
+ integerClass,
"toHexString".toNativeChars(arena),
- "(J)Ljava/lang/String;".toNativeChars(arena));
+ "(I)Ljava/lang/String;".toNativeChars(arena));
- for (var i in [1, 80, 13, 76, 1134453224145]) {
+ for (var i in [1, 80, 13, 76, 11344]) {
// if your argument is int, bool, or JObject (`Pointer<Void>`)
// it can be directly placed in the list. To convert into different primitive
// types, use JValue<Type> wrappers.
- final jres = env.CallStaticObjectMethodA(longClass, hexMethod,
- Jni.jvalues([JValueLong(i)], allocator: arena));
+ final jres = env.CallStaticObjectMethodA(integerClass, hexMethod,
+ Jni.jvalues([JValueInt(i)], allocator: arena));
// use asDartString extension method on Pointer<JniEnv>
// to convert a String jobject result to string
@@ -82,7 +82,7 @@
// java class exists.
env.DeleteGlobalRef(jres);
}
- env.DeleteGlobalRef(longClass);
+ env.DeleteGlobalRef(integerClass);
}));
test("asJString extension method", () {
diff --git a/pkgs/jni/test/jobject_test.dart b/pkgs/jni/test/jobject_test.dart
index 5bafe75..b055f7d 100644
--- a/pkgs/jni/test/jobject_test.dart
+++ b/pkgs/jni/test/jobject_test.dart
@@ -54,7 +54,7 @@
test("call a static method using JniClass APIs", () {
final integerClass = Jni.findJniClass("java/lang/Integer");
final result = integerClass.callStaticMethodByName<JString>(
- "toHexString", "(I)Ljava/lang/String;", [31]);
+ "toHexString", "(I)Ljava/lang/String;", [JValueInt(31)]);
// if the object is supposed to be a Java string
// you can call toDartString on it.
@@ -95,10 +95,9 @@
final nextIntMethod = random.getMethodID("nextInt", "(I)I");
for (int i = 0; i < 100; i++) {
- int r = random.callMethod<int>(nextIntMethod, [256 * 256]);
+ int r = random.callMethod<int>(nextIntMethod, [JValueInt(256 * 256)]);
int bits = 0;
- final jbc =
- longClass.callStaticMethod<int>(bitCountMethod, [JValueLong(r)]);
+ final jbc = longClass.callStaticMethod<int>(bitCountMethod, [r]);
while (r != 0) {
bits += r % 2;
r = (r / 2).floor();
@@ -133,7 +132,7 @@
final longClass = Jni.findJniClass("java/lang/Long");
const n = 1223334444;
final strFromJava = longClass.callStaticMethodByName<String>(
- "toOctalString", "(J)Ljava/lang/String;", [JValueLong(n)]);
+ "toOctalString", "(J)Ljava/lang/String;", [n]);
expect(strFromJava, equals(n.toRadixString(8)));
longClass.delete();
});
@@ -163,8 +162,9 @@
// You can use() method on JObject for using once and deleting.
test("use() method", () {
- final randomInt = Jni.newInstance("java/util/Random", "()V", [])
- .use((random) => random.callMethodByName<int>("nextInt", "(I)I", [15]));
+ final randomInt = Jni.newInstance("java/util/Random", "()V", []).use(
+ (random) =>
+ random.callMethodByName<int>("nextInt", "(I)I", [JValueInt(15)]));
expect(randomInt, lessThan(15));
});
diff --git a/pkgs/jnigen/lib/src/bindings/dart_generator.dart b/pkgs/jnigen/lib/src/bindings/dart_generator.dart
index 6ae3396..db53275 100644
--- a/pkgs/jnigen/lib/src/bindings/dart_generator.dart
+++ b/pkgs/jnigen/lib/src/bindings/dart_generator.dart
@@ -925,13 +925,15 @@
String cCtor(Method node) {
final name = node.finalName;
- final params = node.params.accept(const _ParamCall()).join(', ');
+ final params =
+ node.params.accept(const _ParamCall(isCBased: true)).join(', ');
return '_$name($params)';
}
String dartOnlyCtor(Method node) {
final name = node.finalName;
- final params = node.params.accept(const _ParamCall()).join(', ');
+ final params =
+ node.params.accept(const _ParamCall(isCBased: false)).join(', ');
return '$_accessors.newObjectWithArgs($_classRef, _id_$name, [$params])';
}
@@ -939,7 +941,7 @@
final name = node.finalName;
final params = [
if (!node.isStatic) _selfPointer,
- ...node.params.accept(const _ParamCall()),
+ ...node.params.accept(const _ParamCall(isCBased: true)),
].join(', ');
final resultGetter = node.returnType.accept(const _JniResultGetter());
return '_$name($params).$resultGetter';
@@ -950,7 +952,8 @@
final ifStatic = node.isStatic ? 'Static' : '';
final self = node.isStatic ? _classRef : _selfPointer;
final callType = node.returnType.accept(const _CallType());
- final params = node.params.accept(const _ParamCall()).join(', ');
+ final params =
+ node.params.accept(const _ParamCall(isCBased: false)).join(', ');
final resultGetter = node.returnType.accept(const _JniResultGetter());
return '$_accessors.call${ifStatic}MethodWithArgs($self, _id_$name, $callType, [$params]).$resultGetter';
}
@@ -1105,11 +1108,43 @@
/// void bar(Foo foo) => _bar(foo.reference);
/// ```
class _ParamCall extends Visitor<Param, String> {
- const _ParamCall();
+ final bool isCBased;
+
+ const _ParamCall({required this.isCBased});
@override
String visit(Param node) {
final nativeSuffix = node.type.accept(const _ToNativeSuffix());
- return '${node.finalName}$nativeSuffix';
+ final paramCall = '${node.finalName}$nativeSuffix';
+ if (!isCBased) {
+ // We need to wrap [paramCall] in the appropriate wrapper class.
+ return node.type.accept(_JValueWrapper(paramCall));
+ }
+ return paramCall;
+ }
+}
+
+/// Wraps the parameter in the appropriate JValue wrapper class.
+///
+/// For instance, `int` in Dart can be mapped to `long` or `int` or ... in Java.
+/// The wrapper class is how we identify the type in pure dart bindings.
+class _JValueWrapper extends TypeVisitor<String> {
+ final String param;
+
+ _JValueWrapper(this.param);
+
+ @override
+ String visitNonPrimitiveType(ReferredType node) {
+ return param;
+ }
+
+ @override
+ String visitPrimitiveType(PrimitiveType node) {
+ if (node.name == 'long' ||
+ node.name == 'double' ||
+ node.name == 'boolean') {
+ return param;
+ }
+ return '$_jni.JValue${node.name.capitalize()}($param)';
}
}
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/lib/com/fasterxml/jackson/core/JsonFactory.dart b/pkgs/jnigen/test/jackson_core_test/third_party/lib/com/fasterxml/jackson/core/JsonFactory.dart
index 9c8decb..899ab00 100644
--- a/pkgs/jnigen/test/jackson_core_test/third_party/lib/com/fasterxml/jackson/core/JsonFactory.dart
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/lib/com/fasterxml/jackson/core/JsonFactory.dart
@@ -1018,10 +1018,11 @@
jni.JArray<jni.JByte> data, int offset, int len) =>
const jsonparser_.$JsonParserType().fromRef(jniAccessors
.callMethodWithArgs(
- reference,
- _id_createParser5,
- jni.JniCallType.objectType,
- [data.reference, offset, len]).object);
+ reference, _id_createParser5, jni.JniCallType.objectType, [
+ data.reference,
+ jni.JValueInt(offset),
+ jni.JValueInt(len)
+ ]).object);
static final _id_createParser6 = jniAccessors.getMethodIDOf(
_classRef,
@@ -1065,10 +1066,11 @@
jni.JArray<jni.JChar> content, int offset, int len) =>
const jsonparser_.$JsonParserType().fromRef(jniAccessors
.callMethodWithArgs(
- reference,
- _id_createParser8,
- jni.JniCallType.objectType,
- [content.reference, offset, len]).object);
+ reference, _id_createParser8, jni.JniCallType.objectType, [
+ content.reference,
+ jni.JValueInt(offset),
+ jni.JValueInt(len)
+ ]).object);
static final _id_createParser9 = jniAccessors.getMethodIDOf(
_classRef,
@@ -1414,10 +1416,11 @@
jni.JArray<jni.JByte> data, int offset, int len) =>
const jsonparser_.$JsonParserType().fromRef(jniAccessors
.callMethodWithArgs(
- reference,
- _id_createJsonParser5,
- jni.JniCallType.objectType,
- [data.reference, offset, len]).object);
+ reference, _id_createJsonParser5, jni.JniCallType.objectType, [
+ data.reference,
+ jni.JValueInt(offset),
+ jni.JValueInt(len)
+ ]).object);
static final _id_createJsonParser6 = jniAccessors.getMethodIDOf(
_classRef,
@@ -1610,7 +1613,10 @@
/// from: public boolean enabledIn(int flags)
bool enabledIn(int flags) => jniAccessors.callMethodWithArgs(
- reference, _id_enabledIn, jni.JniCallType.booleanType, [flags]).boolean;
+ reference,
+ _id_enabledIn,
+ jni.JniCallType.booleanType,
+ [jni.JValueInt(flags)]).boolean;
static final _id_getMask =
jniAccessors.getMethodIDOf(_classRef, r"getMask", r"()I");
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/lib/com/fasterxml/jackson/core/JsonParser.dart b/pkgs/jnigen/test/jackson_core_test/third_party/lib/com/fasterxml/jackson/core/JsonParser.dart
index c371560..ee9ceff 100644
--- a/pkgs/jnigen/test/jackson_core_test/third_party/lib/com/fasterxml/jackson/core/JsonParser.dart
+++ b/pkgs/jnigen/test/jackson_core_test/third_party/lib/com/fasterxml/jackson/core/JsonParser.dart
@@ -93,8 +93,8 @@
/// from: protected void <init>(int features)
/// The returned object must be deleted after use, by calling the `delete` method.
JsonParser.ctor1(int features)
- : super.fromRef(jniAccessors
- .newObjectWithArgs(_classRef, _id_ctor1, [features]).object);
+ : super.fromRef(jniAccessors.newObjectWithArgs(
+ _classRef, _id_ctor1, [jni.JValueInt(features)]).object);
static final _id_getCodec = jniAccessors.getMethodIDOf(
_classRef, r"getCodec", r"()Lcom/fasterxml/jackson/core/ObjectCodec;");
@@ -658,8 +658,11 @@
///@since 2.3
///@deprecated Since 2.7, use \#overrideStdFeatures(int, int) instead
JsonParser setFeatureMask(int mask) =>
- const $JsonParserType().fromRef(jniAccessors.callMethodWithArgs(reference,
- _id_setFeatureMask, jni.JniCallType.objectType, [mask]).object);
+ const $JsonParserType().fromRef(jniAccessors.callMethodWithArgs(
+ reference,
+ _id_setFeatureMask,
+ jni.JniCallType.objectType,
+ [jni.JValueInt(mask)]).object);
static final _id_overrideStdFeatures = jniAccessors.getMethodIDOf(_classRef,
r"overrideStdFeatures", r"(II)Lcom/fasterxml/jackson/core/JsonParser;");
@@ -684,7 +687,7 @@
reference,
_id_overrideStdFeatures,
jni.JniCallType.objectType,
- [values, mask]).object);
+ [jni.JValueInt(values), jni.JValueInt(mask)]).object);
static final _id_getFormatFeatures =
jniAccessors.getMethodIDOf(_classRef, r"getFormatFeatures", r"()I");
@@ -721,7 +724,7 @@
reference,
_id_overrideFormatFeatures,
jni.JniCallType.objectType,
- [values, mask]).object);
+ [jni.JValueInt(values), jni.JValueInt(mask)]).object);
static final _id_nextToken = jniAccessors.getMethodIDOf(
_classRef, r"nextToken", r"()Lcom/fasterxml/jackson/core/JsonToken;");
@@ -860,7 +863,7 @@
reference,
_id_nextIntValue,
jni.JniCallType.intType,
- [defaultValue]).integer;
+ [jni.JValueInt(defaultValue)]).integer;
static final _id_nextLongValue =
jniAccessors.getMethodIDOf(_classRef, r"nextLongValue", r"(J)J");
@@ -1058,8 +1061,8 @@
///@param id Token id to match (from (@link JsonTokenId})
///@return {@code True} if the parser current points to specified token
///@since 2.5
- bool hasTokenId(int id) => jniAccessors.callMethodWithArgs(
- reference, _id_hasTokenId, jni.JniCallType.booleanType, [id]).boolean;
+ bool hasTokenId(int id) => jniAccessors.callMethodWithArgs(reference,
+ _id_hasTokenId, jni.JniCallType.booleanType, [jni.JValueInt(id)]).boolean;
static final _id_hasToken = jniAccessors.getMethodIDOf(
_classRef, r"hasToken", r"(Lcom/fasterxml/jackson/core/JsonToken;)Z");
@@ -1798,7 +1801,10 @@
///@throws IOException for low-level read issues, or
/// JsonParseException for decoding problems
int getValueAsInt1(int def) => jniAccessors.callMethodWithArgs(
- reference, _id_getValueAsInt1, jni.JniCallType.intType, [def]).integer;
+ reference,
+ _id_getValueAsInt1,
+ jni.JniCallType.intType,
+ [jni.JValueInt(def)]).integer;
static final _id_getValueAsLong =
jniAccessors.getMethodIDOf(_classRef, r"getValueAsLong", r"()J");
@@ -2282,7 +2288,10 @@
/// from: public boolean enabledIn(int flags)
bool enabledIn(int flags) => jniAccessors.callMethodWithArgs(
- reference, _id_enabledIn, jni.JniCallType.booleanType, [flags]).boolean;
+ reference,
+ _id_enabledIn,
+ jni.JniCallType.booleanType,
+ [jni.JValueInt(flags)]).boolean;
static final _id_getMask =
jniAccessors.getMethodIDOf(_classRef, r"getMask", r"()I");