blob: d1000395f652a32cec3a523d8d71c03d333dd47f [file] [log] [blame]
// 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 'dart:typed_data';
import '../accessors.dart';
import '../jarray.dart';
import '../jni.dart';
import '../jreference.dart';
import '../jvalues.dart';
import '../third_party/generated_bindings.dart';
import '../types.dart';
import 'jbuffer.dart';
final class JByteBufferType extends JObjType<JByteBuffer> {
const JByteBufferType();
@override
String get signature => r"Ljava/nio/ByteBuffer;";
@override
JByteBuffer fromRef(JObjectPtr ref) => JByteBuffer.fromRef(ref);
@override
JObjType get superType => const JBufferType();
@override
final superCount = 2;
@override
int get hashCode => (JByteBufferType).hashCode;
@override
bool operator ==(Object other) {
return other.runtimeType == (JByteBufferType) && other is JByteBufferType;
}
}
/// A byte [JBuffer].
///
/// The bindings for `java.nio.ByteBuffer`.
///
/// This enables fast memory copying between Java and Dart when directly
/// allocated (See [JByteBuffer.allocateDirect]).
///
/// To create a [JByteBuffer] from the content of a [Uint8List],
/// use [JByteBuffer.fromList]. This uses direct allocation to enable fast
/// copying.
///
/// [asUint8List] provides a direct access to the underlying [Uint8List] that
/// this buffer uses. This means any changes to it will change the content of
/// the buffer and vice versa. This can be used to access to [Uint8List] methods
/// such as [Uint8List.setRange].
///
/// Example:
/// ```dart
/// final directBuffer = JByteBuffer.allocateDirect(3);
/// directBuffer.asUint8List().setAll(0, [1, 2, 3]);
/// // The buffer is now 1, 2, 3.
/// ```
///
/// Both the original buffer and the [Uint8List] keep the underlying Java buffer
/// alive. Once all the instances of the original buffer and the lists produced
/// from [asUint8List] are inaccessible both in Java and Dart, Java will
/// correctly garbage collects the buffer and frees its underlying memory.
///
/// Example:
/// ```dart
/// final directBuffer = JByteBuffer.allocateDirect(3);
/// final data = directBuffer.asUint8List();
/// directBuffer.release(); // Releasing the original buffer.
/// data.setAll(0, [1, 2, 3]); // Works! [data] is still accessible.
/// ```
///
/// The original buffer can be [release]d when calling [asUint8List]
/// by setting the `releaseOriginal` parameter to `true`.
///
/// Example:
/// ```dart
/// final directBuffer = JByteBuffer.allocateDirect(3);
/// // [releaseOriginal] is `false` by default.
/// final data1 = directBuffer.asUint8List();
/// directBuffer.nextByte = 42; // No problem!
/// print(data1[0]); // prints 42!
/// final data2 = directBuffer.asUint8List(releaseOriginal: true);
/// // directBuffer.nextByte = 42; // throws [UseAfterReleaseException]!
/// ```
class JByteBuffer extends JBuffer {
@override
// ignore: overridden_fields
late final JObjType<JByteBuffer> $type = type;
JByteBuffer.fromRef(
JObjectPtr ref,
) : super.fromRef(ref);
static final _class = Jni.findJClass(r"java/nio/ByteBuffer");
/// The type which includes information such as the signature of this class.
static const type = JByteBufferType();
static final _allocateDirectId = Jni.accessors.getStaticMethodIDOf(
_class.reference.pointer, r"allocateDirect", r"(I)Ljava/nio/ByteBuffer;");
/// Allocates a new direct byte buffer.
///
/// Throws:
/// * [IllegalArgumentException] - If the capacity is a negative integer
factory JByteBuffer.allocateDirect(int capacity) {
return JByteBuffer.fromRef(
Jni.accessors.callStaticMethodWithArgs(
_class.reference.pointer,
_allocateDirectId,
JniCallType.objectType,
[JValueInt(capacity)]).object,
);
}
static final _allocateId = Jni.accessors.getStaticMethodIDOf(
_class.reference.pointer, r"allocate", r"(I)Ljava/nio/ByteBuffer;");
/// Allocates a new byte buffer.
///
/// Throws:
/// * [IllegalArgumentException] - If the capacity is a negative integer
factory JByteBuffer.allocate(int capacity) {
return const JByteBufferType().fromRef(Jni.accessors
.callStaticMethodWithArgs(_class.reference.pointer, _allocateId,
JniCallType.objectType, [JValueInt(capacity)]).object);
}
static final _wrapWholeId = Jni.accessors.getStaticMethodIDOf(
_class.reference.pointer, r"wrap", r"([B)Ljava/nio/ByteBuffer;");
static final _wrapId = Jni.accessors.getStaticMethodIDOf(
_class.reference.pointer, r"wrap", r"([BII)Ljava/nio/ByteBuffer;");
/// Wraps a byte array into a buffer.
///
/// The new buffer will be backed by the given byte array; that is,
/// modifications to the buffer will cause the array to be modified
/// and vice versa.
static JByteBuffer wrap(
JArray<jbyte> array, [
int? offset,
int? length,
]) {
if (offset == null && length == null) {
return const JByteBufferType().fromRef(
Jni.accessors.callStaticMethodWithArgs(
_class.reference.pointer,
_wrapWholeId,
JniCallType.objectType,
[array.reference.pointer],
).object,
);
}
offset ??= 0;
length ??= array.length - offset;
return const JByteBufferType().fromRef(
Jni.accessors.callStaticMethodWithArgs(
_class.reference.pointer,
_wrapId,
JniCallType.objectType,
[array.reference.pointer, JValueInt(offset), JValueInt(length)],
).object,
);
}
/// Creates a [JByteBuffer] from the content of [list].
///
/// The [JByteBuffer] will be allocated using [JByteBuffer.allocateDirect].
factory JByteBuffer.fromList(Uint8List list) {
final buffer = JByteBuffer.allocateDirect(list.length);
buffer._asUint8ListUnsafe().setAll(0, list);
return buffer;
}
static final _sliceId = Jni.accessors.getMethodIDOf(
_class.reference.pointer, r"slice", r"()Ljava/nio/ByteBuffer;");
/// Creates a new byte buffer whose content is a shared subsequence of this
/// buffer's content.
JByteBuffer slice() {
return const JByteBufferType().fromRef(Jni.accessors.callMethodWithArgs(
reference.pointer, _sliceId, JniCallType.objectType, []).object);
}
static final _duplicateId = Jni.accessors.getMethodIDOf(
_class.reference.pointer, r"duplicate", r"()Ljava/nio/ByteBuffer;");
/// Creates a new byte buffer that shares this buffer's content.
JByteBuffer duplicate() {
return const JByteBufferType().fromRef(Jni.accessors.callMethodWithArgs(
reference.pointer, _duplicateId, JniCallType.objectType, []).object);
}
static final _asReadOnlyBufferId = Jni.accessors.getMethodIDOf(
_class.reference.pointer,
r"asReadOnlyBuffer",
r"()Ljava/nio/ByteBuffer;");
/// Creates a new, read-only byte buffer that shares this buffer's content.
JByteBuffer asReadOnlyBuffer() {
return const JByteBufferType().fromRef(Jni.accessors.callMethodWithArgs(
reference.pointer,
_asReadOnlyBufferId,
JniCallType.objectType, []).object);
}
static final _getId =
Jni.accessors.getMethodIDOf(_class.reference.pointer, r"get", r"()B");
/// Reads the byte at this buffer's current [position], and then increments the
/// [position].
///
/// Throws:
/// * [BufferOverflowException] - If the buffer's current [position] is not
/// smaller than its [limit]
int get nextByte {
return Jni.accessors.callMethodWithArgs(
reference.pointer, _getId, JniCallType.byteType, []).byte;
}
static final _putId = Jni.accessors.getMethodIDOf(
_class.reference.pointer, r"put", r"(B)Ljava/nio/ByteBuffer;");
/// Writes the given byte into this buffer at the current [position], and then
/// increments the [position].
///
/// Throws:
/// * [BufferOverflowException] - If this buffer's current [position] is not
/// smaller than its [limit]
/// * [ReadOnlyBufferException] - If this buffer is read-only
set nextByte(int b) {
Jni.env.DeleteGlobalRef(Jni.accessors.callMethodWithArgs(reference.pointer,
_putId, JniCallType.objectType, [JValueByte(b)]).object);
}
static final _arrayId =
Jni.accessors.getMethodIDOf(_class.reference.pointer, r"array", r"()[B");
@override
JArray<jbyte> get array {
return const JArrayType(jbyteType()).fromRef(Jni.accessors
.callMethodWithArgs(
reference.pointer, _arrayId, JniCallType.objectType, []).object);
}
void _ensureIsDirect() {
if (!isDirect) {
throw StateError(
'The buffer must be created with `JByteBuffer.allocateDirect`.',
);
}
}
Pointer<Void> _directBufferAddress() {
final address = Jni.env.GetDirectBufferAddress(reference.pointer);
if (address == nullptr) {
throw StateError(
'The memory region is undefined or '
'direct buffer access is not supported by this JVM.',
);
}
return address;
}
int _directBufferCapacity() {
final capacity = Jni.env.GetDirectBufferCapacity(reference.pointer);
if (capacity == -1) {
throw StateError(
'The object is an unaligned view buffer and the processor '
'architecture does not support unaligned access.',
);
}
return capacity;
}
Uint8List _asUint8ListUnsafe() {
_ensureIsDirect();
final address = _directBufferAddress();
final capacity = _directBufferCapacity();
return address.cast<Uint8>().asTypedList(capacity);
}
/// Returns this byte buffer as a [Uint8List].
///
/// If [releaseOriginal] is `true`, this byte buffer will be released.
///
/// Throws [StateError] if the buffer is not direct
/// (see [JByteBuffer.allocateDirect]) or the JVM does not support the direct
/// buffer operations or the object is an unaligned view buffer and
/// the processor does not support unaligned access.
Uint8List asUint8List({bool releaseOriginal = false}) {
_ensureIsDirect();
final address = _directBufferAddress();
final capacity = _directBufferCapacity();
final token = releaseOriginal
? reference.pointer
: Jni.env.NewGlobalRef(reference.pointer);
if (releaseOriginal) {
reference.setAsReleased();
}
return address.cast<Uint8>().asTypedList(
capacity,
token: token,
finalizer: Jni.env.ptr.ref.DeleteGlobalRef.cast(),
);
}
}
extension Uint8ListToJava on Uint8List {
/// Creates a [JByteBuffer] from the content of this list.
///
/// The [JByteBuffer] will be allocated using [JByteBuffer.allocateDirect].
JByteBuffer toJByteBuffer() {
return JByteBuffer.fromList(this);
}
}