Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkgs/jni/lib/jni.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,4 @@ export 'src/lang/lang.dart';
export 'src/nio/nio.dart';
export 'src/types.dart' hide JTypeBase, lowestCommonSuperType;
export 'src/util/util.dart';
export 'src/converter.dart';
235 changes: 235 additions & 0 deletions pkgs/jni/lib/src/converter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'jarray.dart';
import 'jni.dart';
import 'jobject.dart';
import 'lang/lang.dart';
import 'types.dart';
import 'util/util.dart';

JObject _defaultJNIConverter(Object o) =>
throw UnimplementedError('No conversion for $o');

/// Converts a Dart object to a JNI object.
JObject? toJObject(
Object? dartObject, {
JObject Function(Object) convertOther = _defaultJNIConverter,
}) =>
switch (dartObject) {
null => null,
JObject() => dartObject,
bool() => dartObject.toJBoolean(),
int() => dartObject.toJLong(),
double() => dartObject.toJDouble(),
String() => dartObject.toJString(),
List<Object?>() => dartObject.toJList(convertOther: convertOther),
Set<Object?>() => dartObject.toJSet(convertOther: convertOther),
Map<Object?, Object?>() => dartObject.toJMap(convertOther: convertOther),
_ => convertOther(dartObject),
};

extension DartListToJList on List<Object?> {
JList<JObject?> toJList({
JObject Function(Object) convertOther = _defaultJNIConverter,
}) {
final list = JList.array(JObject.nullableType);
for (final element in this) {
list.add(toJObject(element, convertOther: convertOther));
}
return list;
}
}

extension DartSetToJSet on Set<Object?> {
JSet<JObject?> toJSet({
JObject Function(Object) convertOther = _defaultJNIConverter,
}) {
final set = JSet.hash(JObject.nullableType);
for (final element in this) {
set.add(toJObject(element, convertOther: convertOther));
}
return set;
}
}

extension DartMapToJMap on Map<Object?, Object?> {
JMap<JObject?, JObject?> toJMap({
JObject Function(Object) convertOther = _defaultJNIConverter,
}) {
final map = JMap.hash(JObject.nullableType, JObject.nullableType);
for (final entry in entries) {
map[toJObject(entry.key, convertOther: convertOther)] =
toJObject(entry.value, convertOther: convertOther);
}
return map;
}
}

extension DartListToJBooleanArray on List<bool> {
JBooleanArray toJBooleanArray() =>
JBooleanArray(length)..setRange(0, length, this);
}

extension DartListToJByteArray on List<int> {
JByteArray toJByteArray() => JByteArray(length)..setRange(0, length, this);
}

extension DartListToJCharArray on List<int> {
JCharArray toJCharArray() => JCharArray(length)..setRange(0, length, this);
}

extension DartListToJShortArray on List<int> {
JShortArray toJShortArray() => JShortArray(length)..setRange(0, length, this);
}

extension DartListToJIntArray on List<int> {
JIntArray toJIntArray() => JIntArray(length)..setRange(0, length, this);
}

extension DartListToJLongArray on List<int> {
JLongArray toJLongArray() => JLongArray(length)..setRange(0, length, this);
}

extension DartListToJFloatArray on List<double> {
JFloatArray toJFloatArray() => JFloatArray(length)..setRange(0, length, this);
}

extension DartListToJDoubleArray on List<double> {
JDoubleArray toJDoubleArray() =>
JDoubleArray(length)..setRange(0, length, this);
}

extension DartListToJObjectArray<E extends JObject?> on List<E> {
JArray<E> toJObjectArray(JType<E> elementType) =>
JArray.of(elementType, this);
}

Object? _defaultDartConverter(JObject? o) => o;

/// Converts a JNI object to a Dart object.
Object? toDartObject(
JObject? jObject, {
Object? Function(JObject?) convertOther = _defaultDartConverter,
}) {
if (jObject == null) return null;

if (jObject.isA(JString.type)) {
return jObject.as(JString.type).toDartString();
}
if (jObject.isA(JBoolean.type)) {
return jObject.as(JBoolean.type).booleanValue();
}
if (jObject.isA(JInteger.type)) {
return jObject.as(JInteger.type).intValue();
}
if (jObject.isA(JLong.type)) {
return jObject.as(JLong.type).longValue();
}
if (jObject.isA(JDouble.type)) {
return jObject.as(JDouble.type).doubleValue();
}
if (jObject.isA(JFloat.type)) {
return jObject.as(JFloat.type).floatValue();
}
if (jObject.isA(JShort.type)) {
return jObject.as(JShort.type).shortValue();
}
if (jObject.isA(JByte.type)) {
return jObject.as(JByte.type).byteValue();
}
if (jObject.isA(JCharacter.type)) {
return jObject.as(JCharacter.type).charValue();
}

if (jObject.isA(JBooleanArray.type)) {
return jObject.as(JBooleanArray.type).toList();
}
if (jObject.isA(JByteArray.type)) {
return jObject.as(JByteArray.type).toList();
}
if (jObject.isA(JCharArray.type)) {
return jObject.as(JCharArray.type).toList();
}
if (jObject.isA(JShortArray.type)) {
return jObject.as(JShortArray.type).toList();
}
if (jObject.isA(JIntArray.type)) {
return jObject.as(JIntArray.type).toList();
}
if (jObject.isA(JLongArray.type)) {
return jObject.as(JLongArray.type).toList();
}
if (jObject.isA(JFloatArray.type)) {
return jObject.as(JFloatArray.type).toList();
}
if (jObject.isA(JDoubleArray.type)) {
return jObject.as(JDoubleArray.type).toList();
}

if (jObject.isA(JArray.type(JObject.nullableType))) {
return jObject
.as(JArray.type(JObject.nullableType))
.toDeepDartList(convertOther: convertOther);
}

if (jObject.isA(JList.type(JObject.nullableType))) {
return jObject
.as(JList.type(JObject.nullableType))
.toDeepDartList(convertOther: convertOther);
}
if (jObject.isA(JSet.type(JObject.nullableType))) {
return jObject
.as(JSet.type(JObject.nullableType))
.toDeepDartSet(convertOther: convertOther);
}
if (jObject.isA(JMap.type(JObject.nullableType, JObject.nullableType))) {
return jObject
.as(JMap.type(JObject.nullableType, JObject.nullableType))
.toDeepDartMap(convertOther: convertOther);
}

return convertOther(jObject);
}

Object? toNullableDartObject(
JObject? jObject, {
Object? Function(JObject?) convertOther = _defaultDartConverter,
}) =>
toDartObject(jObject, convertOther: convertOther);

extension JArrayToDartList<E extends JObject?> on JArray<E> {
List<Object?> toDeepDartList({
Object? Function(JObject?) convertOther = _defaultDartConverter,
}) =>
map((e) => toDartObject(e, convertOther: convertOther)).toList();
}

extension JListToDartList<E extends JObject?> on JList<E> {
List<Object?> toDeepDartList({
Object? Function(JObject?) convertOther = _defaultDartConverter,
}) =>
map((e) => toDartObject(e, convertOther: convertOther)).toList();
}

extension JSetToDartSet<E extends JObject?> on JSet<E> {
Set<Object?> toDeepDartSet({
Object? Function(JObject?) convertOther = _defaultDartConverter,
}) =>
map((e) => toDartObject(e, convertOther: convertOther)).toSet();
}

extension JMapToDartMap<K extends JObject?, V extends JObject?> on JMap<K, V> {
Map<Object?, Object?> toDeepDartMap({
Object? Function(JObject?) convertOther = _defaultDartConverter,
}) =>
Map.fromEntries(
entries.map(
(e) => MapEntry(
toDartObject(e.key, convertOther: convertOther),
toDartObject(e.value, convertOther: convertOther),
),
),
);
}
101 changes: 101 additions & 0 deletions pkgs/jni/test/converter_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:io';

import 'package:jni/jni.dart';
import 'package:test/test.dart';

import 'test_util/test_util.dart';

void main() {
if (!Platform.isAndroid) {
checkDylibIsUpToDate();
spawnJvm();
}
run(testRunner: test);
}

void run({required TestRunnerCallback testRunner}) {
testRunner('toJObject basic types', () {
using((arena) {
expect(toJObject(null), isNull);
expect(toJObject(true)!.isA(JBoolean.type), isTrue);
expect(toJObject(1)!.isA(JLong.type), isTrue);
expect(toJObject(1.5)!.isA(JDouble.type), isTrue);
expect(toJObject('abc')!.isA(JString.type), isTrue);
});
});

testRunner('toDartObject basic types', () {
using((arena) {
expect(toDartObject(null), isNull);
expect(toDartObject(true.toJBoolean()..releasedBy(arena)), true);
expect(toDartObject(1.toJLong()..releasedBy(arena)), 1);
expect(toDartObject(1.5.toJDouble()..releasedBy(arena)), 1.5);
expect(toDartObject('abc'.toJString()..releasedBy(arena)), 'abc');
});
});

testRunner('Deep conversion List', () {
using((arena) {
final list = [1, 'abc', true];
final jList = toJObject(list)!..releasedBy(arena);
expect(jList.isA(JList.type(JObject.nullableType)), isTrue);

final back = toDartObject(jList);
expect(back, isA<List>());
final backList = back as List;
expect(backList[0], 1);
expect(backList[1], 'abc');
expect(backList[2], true);
});
});

testRunner('Deep conversion Map', () {
using((arena) {
final map = {'a': 1, 'b': 2};
final jMap = toJObject(map)!..releasedBy(arena);
expect(jMap.isA(JMap.type(JObject.nullableType, JObject.nullableType)),
isTrue);

final back = toDartObject(jMap);
expect(back, isA<Map>());
final backMap = back as Map;
expect(backMap['a'], 1);
expect(backMap['b'], 2);
});
});

testRunner('Primitive arrays toJ*Array', () {
using((arena) {
final bools = [true, false].toJBooleanArray()..releasedBy(arena);
expect(bools.isA(JBooleanArray.type), isTrue);
expect(toDartObject(bools), [true, false]);

final ints = [1, 2, 3].toJIntArray()..releasedBy(arena);
expect(ints.isA(JIntArray.type), isTrue);
expect(toDartObject(ints), [1, 2, 3]);

final doubles = [1.5, 2.5].toJDoubleArray()..releasedBy(arena);
expect(doubles.isA(JDoubleArray.type), isTrue);
expect(toDartObject(doubles), [1.5, 2.5]);
});
});

testRunner('Object array toJObjectArray', () {
using((arena) {
final strings = [
'a'.toJString()..releasedBy(arena),
'b'.toJString()..releasedBy(arena)
];
final jArray = strings.toJObjectArray(JString.type)..releasedBy(arena);
// Signature for JArray<JString> is [Ljava/lang/String;
expect(jArray.isA(JArray.type(JString.type)), isTrue);

final back = toDartObject(jArray);
expect(back, ['a', 'b']);
});
});
}
Loading