Skip to content

Commit 3c4eb25

Browse files
[jnigen] Array support (#118)
Added support for Java arrays. Closed #69. * `jni`: Added `JniArray<T>` and `JniType<T>` that includes signature information about Java types. * `jnigen` generates an extension for `JniArray<GeneratedClass>` so we can use `operator []` and `operator []=` on all arrays. `jni` types also have this extension. * Arrays also have a `setRange` method similar to `List`s. * `jnigen` generates a private type class that extends `JniType<GeneratedClass>` accessible through `.type` static getter. This is useful for creating arrays since they require a type parameter like `JniArray(JniString.type, 5)` which creates an array of `JniString` with a length of 5.
1 parent dcbbb9b commit 3c4eb25

File tree

34 files changed

+1753
-358
lines changed

34 files changed

+1753
-358
lines changed

pkgs/jni/example/integration_test/on_device_jni_test.dart

+3-3
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,13 @@ void main() {
7575
// Actually it's not even required to get a reference to class
7676
testWidgets("invoke_", (t) async {
7777
final m = Jni.invokeStaticMethod<int>("java/lang/Long", "min", "(JJ)J",
78-
[JValueLong(1234), JValueLong(1324)], JniType.longType);
78+
[JValueLong(1234), JValueLong(1324)], JniCallType.longType);
7979
expect(m, equals(1234));
8080
});
8181

8282
testWidgets("retrieve_", (t) async {
8383
final maxLong = Jni.retrieveStaticField<int>(
84-
"java/lang/Short", "MAX_VALUE", "S", JniType.shortType);
84+
"java/lang/Short", "MAX_VALUE", "S", JniCallType.shortType);
8585
expect(maxLong, equals(32767));
8686
});
8787

@@ -105,7 +105,7 @@ void main() {
105105

106106
testWidgets("Passing strings in arguments", (t) async {
107107
final twelve = Jni.invokeStaticMethod<int>("java/lang/Byte", "parseByte",
108-
"(Ljava/lang/String;)B", ["12"], JniType.byteType);
108+
"(Ljava/lang/String;)B", ["12"], JniCallType.byteType);
109109
expect(twelve, equals(12));
110110
});
111111

pkgs/jni/example/lib/main.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ double randomDouble() {
5151
int uptime() {
5252
return Jni.findJniClass("android/os/SystemClock").use(
5353
(systemClock) => systemClock.callStaticMethodByName<int>(
54-
"uptimeMillis", "()J", [], JniType.longType),
54+
"uptimeMillis", "()J", [], JniCallType.longType),
5555
);
5656
}
5757

pkgs/jni/ffigen.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ headers:
1919
- 'third_party/jni.h' # jni.h from Android NDK
2020
compiler-opts:
2121
- '-Ithird_party/'
22+
enums:
23+
rename:
24+
'JniType': 'JniCallType'
2225
functions:
2326
exclude: # Exclude init functions supposed to be defined in loaded DLL, not JNI
2427
- 'JNI_.*'

pkgs/jni/lib/src/accessors.dart

+5
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ extension JniIdLookupResultMethods on JniPointerResult {
7676
_check(exception);
7777
return id.cast<jfieldID_>();
7878
}
79+
80+
Pointer<Void> get checkedRef {
81+
_check(exception);
82+
return id;
83+
}
7984
}
8085

8186
extension JniClassLookupResultMethods on JniClassLookupResult {

pkgs/jni/lib/src/jni_array.dart

+308
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// ignore_for_file: unnecessary_cast
6+
7+
part of 'jni_object.dart';
8+
9+
class JniArray<E> extends JniObject {
10+
/// The type which includes information such as the signature of this class.
11+
static JniType<JniArray<T>> type<T>(JniType<T> innerType) =>
12+
_JniArrayType(innerType);
13+
14+
/// Construct a new [JniArray] with [reference] as its underlying reference.
15+
JniArray.fromRef(JArray reference) : super.fromRef(reference);
16+
17+
JniArray(JniType<E> typeClass, int length)
18+
: super.fromRef(
19+
(typeClass._type == JniCallType.objectType)
20+
? _accessors
21+
.newObjectArray(
22+
length, typeClass._getClass().reference, nullptr)
23+
.checkedRef
24+
: _accessors
25+
.newPrimitiveArray(length, typeClass._type)
26+
.checkedRef,
27+
);
28+
29+
int? _length;
30+
31+
JniResult elementAt(int index, int type) {
32+
RangeError.checkValidIndex(index, this);
33+
return _accessors.getArrayElement(reference, index, type);
34+
}
35+
36+
/// The number of elements in this array.
37+
int get length {
38+
return _length ??= _env.GetArrayLength(reference);
39+
}
40+
}
41+
42+
extension NativeJniArray<E extends NativeType> on JniArray<E> {
43+
void _allocate(int size, void Function(Pointer<E> ptr) use) {
44+
using((arena) {
45+
final ptr = arena.allocate<E>(size);
46+
use(ptr);
47+
}, malloc);
48+
}
49+
}
50+
51+
extension BoolJniArray on JniArray<JBoolean> {
52+
bool operator [](int index) {
53+
return elementAt(index, JniCallType.booleanType).boolean;
54+
}
55+
56+
void operator []=(int index, bool value) {
57+
RangeError.checkValidIndex(index, this);
58+
_allocate(sizeOf<JBoolean>(), (ptr) {
59+
ptr.value = value ? 1 : 0;
60+
_env.SetBooleanArrayRegion(reference, index, 1, ptr);
61+
});
62+
}
63+
64+
void setRange(int start, int end, Iterable<bool> iterable,
65+
[int skipCount = 0]) {
66+
RangeError.checkValidRange(start, end, length);
67+
final size = end - start;
68+
final it = iterable.skip(skipCount).take(size);
69+
_allocate(sizeOf<JBoolean>() * size, (ptr) {
70+
it.forEachIndexed((index, element) {
71+
ptr[index] = element ? 1 : 0;
72+
});
73+
_env.SetBooleanArrayRegion(reference, start, size, ptr);
74+
});
75+
}
76+
}
77+
78+
extension ByteJniArray on JniArray<JByte> {
79+
int operator [](int index) {
80+
return elementAt(index, JniCallType.byteType).byte;
81+
}
82+
83+
void operator []=(int index, int value) {
84+
RangeError.checkValidIndex(index, this);
85+
_allocate(sizeOf<JByte>(), (ptr) {
86+
ptr.value = value;
87+
_env.SetByteArrayRegion(reference, index, 1, ptr);
88+
});
89+
}
90+
91+
void setRange(int start, int end, Iterable<int> iterable,
92+
[int skipCount = 0]) {
93+
RangeError.checkValidRange(start, end, length);
94+
final size = end - start;
95+
final it = iterable.skip(skipCount).take(size);
96+
_allocate(sizeOf<JByte>() * size, (ptr) {
97+
it.forEachIndexed((index, element) {
98+
ptr[index] = element;
99+
});
100+
_env.SetByteArrayRegion(reference, start, size, ptr);
101+
});
102+
}
103+
}
104+
105+
extension CharJniArray on JniArray<JChar> {
106+
String operator [](int index) {
107+
return String.fromCharCode(
108+
elementAt(index, JniCallType.charType).char,
109+
);
110+
}
111+
112+
void operator []=(int index, String value) {
113+
RangeError.checkValidIndex(index, this);
114+
_allocate(sizeOf<JChar>(), (ptr) {
115+
ptr.value = value.codeUnits.first;
116+
_env.SetCharArrayRegion(reference, index, 1, ptr);
117+
});
118+
}
119+
120+
void setRange(int start, int end, Iterable<String> iterable,
121+
[int skipCount = 0]) {
122+
RangeError.checkValidRange(start, end, length);
123+
final size = end - start;
124+
final it = iterable.skip(skipCount).take(size);
125+
_allocate(sizeOf<JChar>() * size, (ptr) {
126+
it.forEachIndexed((index, element) {
127+
ptr[index] = element.codeUnits.first;
128+
});
129+
_env.SetCharArrayRegion(reference, start, size, ptr);
130+
});
131+
}
132+
}
133+
134+
extension ShortJniArray on JniArray<JShort> {
135+
int operator [](int index) {
136+
return elementAt(index, JniCallType.shortType).short;
137+
}
138+
139+
void operator []=(int index, int value) {
140+
RangeError.checkValidIndex(index, this);
141+
_allocate(sizeOf<JShort>(), (ptr) {
142+
ptr.value = value;
143+
_env.SetShortArrayRegion(reference, index, 1, ptr);
144+
});
145+
}
146+
147+
void setRange(int start, int end, Iterable<int> iterable,
148+
[int skipCount = 0]) {
149+
RangeError.checkValidRange(start, end, length);
150+
final size = end - start;
151+
final it = iterable.skip(skipCount).take(size);
152+
_allocate(sizeOf<JShort>() * size, (ptr) {
153+
it.forEachIndexed((index, element) {
154+
ptr[index] = element;
155+
});
156+
_env.SetShortArrayRegion(reference, start, size, ptr);
157+
});
158+
}
159+
}
160+
161+
extension IntJniArray on JniArray<JInt> {
162+
int operator [](int index) {
163+
return elementAt(index, JniCallType.intType).integer;
164+
}
165+
166+
void operator []=(int index, int value) {
167+
RangeError.checkValidIndex(index, this);
168+
_allocate(sizeOf<JInt>(), (ptr) {
169+
ptr.value = value;
170+
_env.SetIntArrayRegion(reference, index, 1, ptr);
171+
});
172+
}
173+
174+
void setRange(int start, int end, Iterable<int> iterable,
175+
[int skipCount = 0]) {
176+
RangeError.checkValidRange(start, end, length);
177+
final size = end - start;
178+
final it = iterable.skip(skipCount).take(size);
179+
_allocate(sizeOf<JInt>() * size, (ptr) {
180+
it.forEachIndexed((index, element) {
181+
ptr[index] = element;
182+
});
183+
_env.SetIntArrayRegion(reference, start, size, ptr);
184+
});
185+
}
186+
}
187+
188+
extension LongJniArray on JniArray<JLong> {
189+
int operator [](int index) {
190+
return elementAt(index, JniCallType.longType).long;
191+
}
192+
193+
void operator []=(int index, int value) {
194+
RangeError.checkValidIndex(index, this);
195+
_allocate(sizeOf<JLong>(), (ptr) {
196+
ptr.value = value;
197+
_env.SetLongArrayRegion(reference, index, 1, ptr);
198+
});
199+
}
200+
201+
void setRange(int start, int end, Iterable<int> iterable,
202+
[int skipCount = 0]) {
203+
RangeError.checkValidRange(start, end, length);
204+
final size = end - start;
205+
final it = iterable.skip(skipCount).take(size);
206+
_allocate(sizeOf<JLong>() * size, (ptr) {
207+
it.forEachIndexed((index, element) {
208+
ptr[index] = element;
209+
});
210+
_env.SetLongArrayRegion(reference, start, size, ptr);
211+
});
212+
}
213+
}
214+
215+
extension FloatJniArray on JniArray<JFloat> {
216+
double operator [](int index) {
217+
return elementAt(index, JniCallType.floatType).float;
218+
}
219+
220+
void operator []=(int index, double value) {
221+
RangeError.checkValidIndex(index, this);
222+
_allocate(sizeOf<JFloat>(), (ptr) {
223+
ptr.value = value;
224+
_env.SetFloatArrayRegion(reference, index, 1, ptr);
225+
});
226+
}
227+
228+
void setRange(int start, int end, Iterable<double> iterable,
229+
[int skipCount = 0]) {
230+
RangeError.checkValidRange(start, end, length);
231+
final size = end - start;
232+
final it = iterable.skip(skipCount).take(size);
233+
_allocate(sizeOf<JFloat>() * size, (ptr) {
234+
it.forEachIndexed((index, element) {
235+
ptr[index] = element;
236+
});
237+
_env.SetFloatArrayRegion(reference, start, size, ptr);
238+
});
239+
}
240+
}
241+
242+
extension DoubleJniArray on JniArray<JDouble> {
243+
double operator [](int index) {
244+
return elementAt(index, JniCallType.doubleType).doubleFloat;
245+
}
246+
247+
void operator []=(int index, double value) {
248+
RangeError.checkValidIndex(index, this);
249+
_allocate(sizeOf<JDouble>(), (ptr) {
250+
ptr.value = value;
251+
_env.SetDoubleArrayRegion(reference, index, 1, ptr);
252+
});
253+
}
254+
255+
void setRange(int start, int end, Iterable<double> iterable,
256+
[int skipCount = 0]) {
257+
RangeError.checkValidRange(start, end, length);
258+
final size = end - start;
259+
final it = iterable.skip(skipCount).take(size);
260+
_allocate(sizeOf<JDouble>() * size, (ptr) {
261+
it.forEachIndexed((index, element) {
262+
ptr[index] = element;
263+
});
264+
_env.SetDoubleArrayRegion(reference, start, size, ptr);
265+
});
266+
}
267+
}
268+
269+
extension ObjectJniArray<T extends JniObject> on JniArray<T> {
270+
JniObject operator [](int index) {
271+
return JniObject.fromRef(elementAt(index, JniCallType.objectType).object);
272+
}
273+
274+
void operator []=(int index, JniObject value) {
275+
RangeError.checkValidIndex(index, this);
276+
_env.SetObjectArrayElement(reference, index, value.reference);
277+
}
278+
279+
void setRange(int start, int end, Iterable<JniObject> iterable,
280+
[int skipCount = 0]) {
281+
RangeError.checkValidRange(start, end, length);
282+
final size = end - start;
283+
final it = iterable.skip(skipCount).take(size);
284+
it.forEachIndexed((index, element) {
285+
this[index] = element;
286+
});
287+
}
288+
}
289+
290+
extension ArrayJniArray<T> on JniArray<JniArray<T>> {
291+
JniArray<T> operator [](int index) {
292+
return JniArray<T>.fromRef(elementAt(index, JniCallType.objectType).object);
293+
}
294+
295+
void operator []=(int index, JniArray<T> value) {
296+
(this as JniArray<JniObject>)[index] = value;
297+
}
298+
}
299+
300+
extension StringJniArray on JniArray<JniString> {
301+
JniString operator [](int index) {
302+
return JniString.fromRef(elementAt(index, JniCallType.objectType).object);
303+
}
304+
305+
void operator []=(int index, JniString value) {
306+
(this as JniArray<JniObject>)[index] = value;
307+
}
308+
}

pkgs/jni/lib/src/jni_exceptions.dart

+10-10
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,16 @@ class NoJvmInstanceException implements Exception {
5353

5454
extension JniTypeNames on int {
5555
static const _names = {
56-
JniType.booleanType: 'bool',
57-
JniType.byteType: 'byte',
58-
JniType.shortType: 'short',
59-
JniType.charType: 'char',
60-
JniType.intType: 'int',
61-
JniType.longType: 'long',
62-
JniType.floatType: 'float',
63-
JniType.doubleType: 'double',
64-
JniType.objectType: 'object',
65-
JniType.voidType: 'void',
56+
JniCallType.booleanType: 'bool',
57+
JniCallType.byteType: 'byte',
58+
JniCallType.shortType: 'short',
59+
JniCallType.charType: 'char',
60+
JniCallType.intType: 'int',
61+
JniCallType.longType: 'long',
62+
JniCallType.floatType: 'float',
63+
JniCallType.doubleType: 'double',
64+
JniCallType.objectType: 'object',
65+
JniCallType.voidType: 'void',
6666
};
6767
String str() => _names[this]!;
6868
}

0 commit comments

Comments
 (0)