// 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 { @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 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` and `Foo` 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` as well. /// /// For example: /// /// ```dart /// if (object.isA(JLong.type)) { /// final i = object.as(JLong.type).longValue; /// ... /// } /// ``` bool isA(JType 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( JType 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 on T { /// Applies [callback] on this object and then delete the underlying JNI /// reference, returning the result of [callback]. R use(R Function(T) callback) { try { final result = callback(this); return result; } finally { this?.release(); } } }