blob: d6a89c797ee41423f52a2bba6a3aeed97d96e56c [file] [log] [blame] [edit]
// 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:meta/meta.dart' show internal;
import '../jni.dart';
import 'jreference.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 JObjectNullableType extends JObjType<JObject?> {
@internal
const JObjectNullableType();
@internal
@override
String get signature => 'Ljava/lang/Object;';
@internal
@override
JObject? fromReference(JReference reference) =>
reference.isNull ? null : JObject.fromReference(reference);
@internal
@override
JObjType get superType => const JObjectNullableType();
@internal
@override
JObjType get nullableType => this;
// TODO(#70): Once interface implementation lands, other than [superType],
// we should have a list of implemented interfaces.
@internal
@override
final int superCount = 0;
@override
int get hashCode => (JObjectNullableType).hashCode;
@override
bool operator ==(Object other) {
return other.runtimeType == JObjectNullableType &&
other is JObjectNullableType;
}
}
final class JObjectType extends JObjType<JObject> {
@internal
const JObjectType();
@internal
@override
String get signature => 'Ljava/lang/Object;';
@internal
@override
JObject fromReference(JReference reference) =>
JObject.fromReference(reference);
@internal
@override
JObjType get superType => const JObjectType();
@internal
@override
JObjType get nullableType => const JObjectNullableType();
// TODO(#70): Once interface implementation lands, other than [superType],
// we should have a list of implemented interfaces.
@internal
@override
final int superCount = 0;
@override
int get hashCode => (JObjectType).hashCode;
@override
bool operator ==(Object other) {
return other.runtimeType == JObjectType && other is JObjectType;
}
}
/// 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 JObjType<JObject> $type = type;
/// The type which includes information such as the signature of this class.
static const JObjType<JObject> type = JObjectType();
static const JObjType<JObject?> nullableType = JObjectNullableType();
/// 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?>(JObjType<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?>(
JObjType<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 JStringType(), [])
.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();
}
}
}