blob: 0b577625aeeb92d1f19a9d0f1601171acbb52b53 [file] [log] [blame] [edit]
// 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 'package:meta/meta.dart' show internal;
import '../jarray.dart';
import '../jni.dart';
import '../jobject.dart';
import '../jreference.dart';
import '../jvalues.dart';
import '../types.dart';
import 'jbuffer.dart';
@internal
final class $JByteBuffer$NullableType$ extends JType<JByteBuffer?> {
const $JByteBuffer$NullableType$();
@override
String get signature => r'Ljava/nio/ByteBuffer;';
@override
JByteBuffer? fromReference(JReference reference) =>
reference.isNull ? null : JByteBuffer.fromReference(reference);
@override
JType get superType => const $JByteBuffer$NullableType$();
@override
JType<JByteBuffer?> get nullableType => this;
@override
final superCount = 2;
@override
int get hashCode => ($JByteBuffer$NullableType$).hashCode;
@override
bool operator ==(Object other) {
return other.runtimeType == $JByteBuffer$NullableType$ &&
other is $JByteBuffer$NullableType$;
}
}
@internal
final class $JByteBuffer$Type$ extends JType<JByteBuffer> {
const $JByteBuffer$Type$();
@override
String get signature => r'Ljava/nio/ByteBuffer;';
@override
JByteBuffer fromReference(JReference reference) =>
JByteBuffer.fromReference(reference);
@override
JType get superType => const $JBuffer$Type$();
@override
JType<JByteBuffer?> get nullableType => const $JByteBuffer$NullableType$();
@override
final superCount = 2;
@override
int get hashCode => ($JByteBuffer$Type$).hashCode;
@override
bool operator ==(Object other) {
return other.runtimeType == $JByteBuffer$Type$ &&
other is $JByteBuffer$Type$;
}
}
/// 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 {
@internal
@override
// ignore: overridden_fields
final JType<JByteBuffer> $type = type;
JByteBuffer.fromReference(
super.reference,
) : super.fromReference();
static final _class = JClass.forName(r'java/nio/ByteBuffer');
/// The type which includes information such as the signature of this class.
static const JType<JByteBuffer> type = $JByteBuffer$Type$();
/// The type which includes information such as the signature of this class.
static const JType<JByteBuffer?> nullableType = $JByteBuffer$NullableType$();
static final _allocateDirectId =
_class.staticMethodId(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 _allocateDirectId(
_class, const $JByteBuffer$Type$(), [JValueInt(capacity)])!;
}
static final _allocateId =
_class.staticMethodId(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 _allocateId(
_class, const $JByteBuffer$Type$(), [JValueInt(capacity)])!;
}
static final _wrapWholeId =
_class.staticMethodId(r'wrap', r'([B)Ljava/nio/ByteBuffer;');
static final _wrapId =
_class.staticMethodId(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(
JByteArray array, [
int? offset,
int? length,
]) {
final arrayRef = array.reference;
if (offset == null && length == null) {
return _wrapWholeId(
_class,
const $JByteBuffer$Type$(),
[arrayRef.pointer],
)!;
}
offset ??= 0;
length ??= array.length - offset;
return _wrapId(
_class,
const $JByteBuffer$Type$(),
[arrayRef.pointer, JValueInt(offset), JValueInt(length)],
)!;
}
/// 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 =
_class.instanceMethodId(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 _sliceId(this, const $JByteBuffer$Type$(), [])!;
}
static final _duplicateId =
_class.instanceMethodId(r'duplicate', r'()Ljava/nio/ByteBuffer;');
/// Creates a new byte buffer that shares this buffer's content.
JByteBuffer duplicate() {
return _duplicateId(this, const $JByteBuffer$Type$(), [])!;
}
static final _asReadOnlyBufferId =
_class.instanceMethodId(r'asReadOnlyBuffer', r'()Ljava/nio/ByteBuffer;');
/// Creates a new, read-only byte buffer that shares this buffer's content.
JByteBuffer asReadOnlyBuffer() {
return _asReadOnlyBufferId(this, const $JByteBuffer$Type$(), [])!;
}
static final _getId = _class.instanceMethodId(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 _getId(this, const jbyteType(), []);
}
static final _putId =
_class.instanceMethodId(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) {
_putId(this, const $JObject$Type$(), [JValueByte(b)]).release();
}
static final _arrayId = _class.instanceMethodId(r'array', r'()[B');
@override
JByteArray get array {
return _arrayId(this, JByteArray.type, [])!;
}
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);
}
}