| // Copyright (c) 2022, 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 'package:ffi/ffi.dart'; | |
| import 'package:meta/meta.dart' show internal; | |
| import 'core_bindings.dart'; | |
| import 'errors.dart'; | |
| import 'jni.dart'; | |
| import 'jreference.dart'; | |
| import 'lang/jstring.dart'; | |
| import 'types.dart'; | |
| /// Error thrown when casting between incompatible [JObject] subclasses. | |
| final class CastError extends Error { | |
| final String _message; | |
| CastError(this._message); | |
| @override | |
| String toString() { | |
| return _message; | |
| } | |
| } | |
| final class $JObject$Type$ extends JType<JObject> { | |
| @internal | |
| const $JObject$Type$(); | |
| @internal | |
| @override | |
| String get signature => r'Ljava/lang/Object;'; | |
| } | |
| /// A high-level wrapper for JNI global object reference. | |
| /// | |
| /// This is the base class for classes generated by `jnigen`. | |
| class JObject { | |
| @internal | |
| final JReference reference; | |
| /// The type which includes information such as the signature of this class. | |
| static const JType<JObject> type = $JObject$Type$(); | |
| /// Constructs a [JObject] with the underlying [reference]. | |
| JObject.fromReference(this.reference) { | |
| if (reference.isNull) { | |
| throw JNullError(); | |
| } | |
| } | |
| /// Returns [JClass] corresponding to concrete class of this object. | |
| /// | |
| /// This may be a subclass of compile-time class. | |
| JClass get jClass { | |
| final classRef = Jni.env.GetObjectClass(reference.pointer); | |
| if (classRef == nullptr) { | |
| Jni.throwException(Jni.env.ExceptionOccurred()); | |
| } | |
| return JClass.fromReference(JGlobalReference(classRef)); | |
| } | |
| /// Releases the underlying [reference]. | |
| /// | |
| /// Releasing in one isolate while using or releasing in another isolate might | |
| /// crash in the JNI layer. | |
| void release() { | |
| reference.release(); | |
| } | |
| /// Whether this object is of the given [type] ignoring the type parameters. | |
| /// | |
| /// > [!WARNING] | |
| /// > Because of Java generic type erasure, this method cannot distinguish | |
| /// > between two classes `Foo<A>` and `Foo<B>` as they are both of type | |
| /// > `Foo`. Therefore, `object.isA(Foo.type(A.type))` will return a | |
| /// > false-positive `true` for objects of type `Foo<B>` as well. | |
| /// | |
| /// For example: | |
| /// | |
| /// <!-- file://./../../tool/snippets/jobject_snippet.dart --> | |
| /// ```dart | |
| /// if (object.isA(JLong.type)) { | |
| /// final i = object.as(JLong.type).longValue(); | |
| /// } | |
| /// ``` | |
| bool isA<T extends JObject>(JType<T> type) { | |
| final targetJClass = type.jClass; | |
| final canBeCasted = isInstanceOf(targetJClass); | |
| targetJClass.release(); | |
| return canBeCasted; | |
| } | |
| /// Whether this object is of the type of the given [jclass]. | |
| bool isInstanceOf(JClass jclass) { | |
| return Jni.env.IsInstanceOf(reference.pointer, jclass.reference.pointer); | |
| } | |
| /// Casts this object to another [type]. | |
| /// | |
| /// If [releaseOriginal] is `true`, the casted object will be released. | |
| /// | |
| /// Throws [CastError] if this object is not an instance of [type]. | |
| T as<T extends JObject>( | |
| JType<T> type, { | |
| bool releaseOriginal = false, | |
| }) { | |
| if (!isA(type)) { | |
| throw CastError('not a subtype of "${type.signature}"'); | |
| } | |
| if (releaseOriginal) { | |
| final ret = | |
| JObject.fromReference(JGlobalReference(reference.pointer)) as T; | |
| reference.setAsReleased(); | |
| return ret; | |
| } | |
| final newRef = JGlobalReference(Jni.env.NewGlobalRef(reference.pointer)); | |
| return JObject.fromReference(newRef) as T; | |
| } | |
| static final _class = JClass.forName('java/lang/Object'); | |
| static final _hashCodeId = _class.instanceMethodId(r'hashCode', r'()I'); | |
| @override | |
| int get hashCode => _hashCodeId(this, const jintType(), [])!; | |
| static final _equalsId = | |
| _class.instanceMethodId(r'equals', r'(Ljava/lang/Object;)Z'); | |
| @override | |
| bool operator ==(Object other) { | |
| if (other is! JObject) { | |
| return false; | |
| } | |
| final otherRef = other.reference; | |
| return _equalsId(this, const jbooleanType(), [otherRef.pointer])!; | |
| } | |
| static final _toStringId = | |
| _class.instanceMethodId(r'toString', r'()Ljava/lang/String;'); | |
| @override | |
| String toString() { | |
| return _toStringId(this, JString.type, []) | |
| .toDartString(releaseOriginal: true); | |
| } | |
| bool get isReleased => reference.isReleased; | |
| /// Registers this object to be released at the end of [arena]'s lifetime. | |
| void releasedBy(Arena arena) { | |
| assert(() { | |
| if (Jni.captureStackTraceOnRelease && reference is JGlobalReference) { | |
| (reference as JGlobalReference).registeredInArena(); | |
| } | |
| return true; | |
| }()); | |
| arena.onReleaseAll(() { | |
| if (!isReleased) { | |
| release(); | |
| } | |
| }); | |
| } | |
| } | |
| /// A high-level wrapper for JNI global object reference of a | |
| /// `java.lang.Throwable`. | |
| /// | |
| /// This is the base class for all exceptions generated by `jnigen`. | |
| class JThrowable extends JObject implements Exception { | |
| final String message; | |
| final String javaStackTrace; | |
| @internal | |
| JThrowable.fromReference( | |
| super.reference, | |
| this.message, | |
| this.javaStackTrace, | |
| ) : super.fromReference(); | |
| @override | |
| String toString() => 'Exception in Java: $message\n$javaStackTrace'; | |
| } | |
| extension JObjectUseExtension<T extends JObject?> on T { | |
| /// Applies [callback] on this object and then deletes the underlying JNI | |
| /// reference, returning the result of [callback]. | |
| R use<R>(R Function(T) callback) { | |
| try { | |
| final result = callback(this); | |
| return result; | |
| } finally { | |
| this?.release(); | |
| } | |
| } | |
| } |