From ba0932417c6b393bea82070b8e768edccc084e1f Mon Sep 17 00:00:00 2001 From: Hossein Yousefi Date: Tue, 26 Sep 2023 13:50:40 +0200 Subject: [PATCH] Add `JLazyReference` and `JFinalString` (#400) --- jni/lib/src/jfinal_string.dart | 31 +++++++++++++++++++++ jni/lib/src/jreference.dart | 46 ++++++++++++++++++++++++++++++-- jni/test/jfinal_string_test.dart | 43 +++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 jni/lib/src/jfinal_string.dart create mode 100644 jni/test/jfinal_string_test.dart diff --git a/jni/lib/src/jfinal_string.dart b/jni/lib/src/jfinal_string.dart new file mode 100644 index 00000000..ce327998 --- /dev/null +++ b/jni/lib/src/jfinal_string.dart @@ -0,0 +1,31 @@ +// Copyright (c) 2023, 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:ffi'; + +import 'jreference.dart'; +import 'lang/jstring.dart'; +import 'third_party/generated_bindings.dart'; + +/// Used for `static final` Java strings, where the constant string is +/// available. +/// +/// If only its value is used using [toDartString], the [reference] is never +/// populated, saving a method call. +class JFinalString extends JString with JLazyReference { + @override + final JObjectPtr Function() lazyReference; + + final String string; + + JFinalString(this.lazyReference, this.string) : super.fromRef(nullptr); + + @override + String toDartString({bool releaseOriginal = false}) { + if (releaseOriginal) { + release(); + } + return string; + } +} diff --git a/jni/lib/src/jreference.dart b/jni/lib/src/jreference.dart index 8d8b71dc..093e297d 100644 --- a/jni/lib/src/jreference.dart +++ b/jni/lib/src/jreference.dart @@ -29,8 +29,9 @@ extension ProtectedJReference on JReference { /// /// Detaches the finalizer so the underlying pointer will not be deleted. JObjectPtr toPointer() { + final ref = reference; setAsReleased(); - return _reference; + return ref; } } @@ -42,7 +43,9 @@ abstract class JReference implements Finalizable { NativeFinalizer(Jni.env.ptr.ref.DeleteGlobalRef.cast()); JReference.fromRef(this._reference) { - _finalizer.attach(this, _reference, detach: this); + if (_reference != nullptr) { + _finalizer.attach(this, _reference, detach: this); + } } bool _released = false; @@ -80,6 +83,45 @@ abstract class JReference implements Finalizable { void releasedBy(Arena arena) => arena.onReleaseAll(release); } +/// Creates a "lazy" [JReference]. +/// +/// The first use of [reference] will call [lazyReference]. +/// +/// This is useful when the Java object is not necessarily used directly, and +/// there are alternative ways to get a Dart representation of the Object. +/// +/// Object mixed in with this must call their super.[fromRef] constructor +/// with [nullptr]. +/// +/// Also see [JFinalString]. +mixin JLazyReference on JReference { + JObjectPtr? _lazyReference; + + JObjectPtr Function() get lazyReference; + + @override + JObjectPtr get reference { + if (_lazyReference == null) { + _lazyReference = lazyReference(); + JReference._finalizer.attach(this, _lazyReference!, detach: this); + return _lazyReference!; + } + if (_released) { + throw UseAfterReleaseError(); + } + return _lazyReference!; + } + + @override + void release() { + setAsReleased(); + if (_lazyReference == null) { + return; + } + Jni.env.DeleteGlobalRef(_lazyReference!); + } +} + extension JReferenceUseExtension on T { /// Applies [callback] on [this] object and then delete the underlying JNI /// reference, returning the result of [callback]. diff --git a/jni/test/jfinal_string_test.dart b/jni/test/jfinal_string_test.dart new file mode 100644 index 00000000..c8a99800 --- /dev/null +++ b/jni/test/jfinal_string_test.dart @@ -0,0 +1,43 @@ +// Copyright (c) 2023, 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:jni/src/jfinal_string.dart'; +import 'package:test/test.dart'; + +import 'test_util/test_util.dart'; + +void main() { + // Don't forget to initialize JNI. + if (!Platform.isAndroid) { + checkDylibIsUpToDate(); + Jni.spawnIfNotExists(dylibDir: "build/jni_libs", jvmOptions: ["-Xmx128m"]); + } + run(testRunner: test); +} + +void run({required TestRunnerCallback testRunner}) { + testRunner('JFinalString', () { + const string = 'abc'; + var referenceFetchedCount = 0; + final finalString = JFinalString( + () { + ++referenceFetchedCount; + return string.toJString().reference; + }, + string, + ); + expect(finalString.toDartString(), string); + expect(referenceFetchedCount, 0); + expect(finalString.reference, isNot(nullptr)); + expect(referenceFetchedCount, 1); + finalString.reference; + expect(referenceFetchedCount, 1); + expect(finalString.toDartString(releaseOriginal: true), string); + expect(() => finalString.reference, throwsA(isA())); + expect(finalString.release, throwsA(isA())); + }); +}