| // 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 '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; |
| } |
| } |
| |
| @internal |
| final class $JObject$NullableType$ extends JType<JObject?> { |
| const $JObject$NullableType$(); |
| |
| @override |
| String get signature => 'Ljava/lang/Object;'; |
| |
| @override |
| JObject? fromReference(JReference reference) => |
| reference.isNull ? null : JObject.fromReference(reference); |
| |
| @override |
| JType get superType => const $JObject$NullableType$(); |
| |
| @override |
| JType get nullableType => this; |
| |
| // TODO(#70): Once interface implementation lands, other than [superType], |
| // we should have a list of implemented interfaces. |
| |
| @override |
| final int superCount = 0; |
| |
| @override |
| int get hashCode => ($JObject$NullableType$).hashCode; |
| |
| @override |
| bool operator ==(Object other) { |
| return other.runtimeType == $JObject$NullableType$ && |
| other is $JObject$NullableType$; |
| } |
| } |
| |
| @internal |
| final class $JObject$Type$ extends JType<JObject> { |
| const $JObject$Type$(); |
| |
| @override |
| String get signature => 'Ljava/lang/Object;'; |
| |
| @override |
| JObject fromReference(JReference reference) => |
| JObject.fromReference(reference); |
| |
| @override |
| JType get superType => const $JObject$Type$(); |
| |
| @override |
| JType get nullableType => const $JObject$NullableType$(); |
| |
| // TODO(#70): Once interface implementation lands, other than [superType], |
| // we should have a list of implemented interfaces. |
| |
| @override |
| final int superCount = 0; |
| |
| @override |
| int get hashCode => ($JObject$Type$).hashCode; |
| |
| @override |
| bool operator ==(Object other) { |
| return other.runtimeType == $JObject$Type$ && other is $JObject$Type$; |
| } |
| } |
| |
| /// 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; |
| |
| @internal |
| final JType<JObject> $type = type; |
| |
| /// The type which includes information such as the signature of this class. |
| static const JType<JObject> type = $JObject$Type$(); |
| |
| /// The type which includes information such as the signature of this class. |
| static const JType<JObject?> nullableType = $JObject$NullableType$(); |
| |
| /// 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: |
| /// |
| /// ```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 = type.fromReference(JGlobalReference(reference.pointer)); |
| reference.setAsReleased(); |
| return ret; |
| } |
| final newRef = JGlobalReference(Jni.env.NewGlobalRef(reference.pointer)); |
| return type.fromReference(newRef); |
| } |
| |
| 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, const $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) => arena.onReleaseAll(release); |
| } |
| |
| extension JObjectUseExtension<T extends JObject?> on T { |
| /// Applies [callback] on this object and then delete 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(); |
| } |
| } |
| } |