mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 00:39:49 +00:00
6f00b19174
Change-Id: Ib1127853bc50682e674ccc783f958e7865118b6a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/125063 Reviewed-by: Sigmund Cherem <sigmund@google.com> Reviewed-by: Nicholas Shahan <nshahan@google.com> Commit-Queue: Mark Zhou <markzipan@google.com>
323 lines
9.5 KiB
Dart
323 lines
9.5 KiB
Dart
// Copyright (c) 2013, 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.
|
|
|
|
part of dart.convert;
|
|
|
|
/// This class provides an interface for converters to
|
|
/// efficiently transmit String data.
|
|
///
|
|
/// Instead of limiting the interface to one non-chunked String it accepts
|
|
/// partial strings or can be transformed into a byte sink that
|
|
/// accepts UTF-8 code units.
|
|
///
|
|
/// This abstract class will likely get more methods over time. Implementers are
|
|
/// urged to extend [StringConversionSinkBase] or to mix in
|
|
/// [StringConversionSinkMixin], to ensure that their class covers the newly
|
|
/// added methods.
|
|
abstract class StringConversionSink extends ChunkedConversionSink<String> {
|
|
StringConversionSink();
|
|
factory StringConversionSink.withCallback(void callback(String accumulated)) =
|
|
_StringCallbackSink;
|
|
factory StringConversionSink.from(Sink<String> sink) = _StringAdapterSink;
|
|
|
|
/// Creates a new instance wrapping the given [sink].
|
|
///
|
|
/// Every string that is added to the returned instance is forwarded to
|
|
/// the [sink]. The instance is allowed to buffer and is not required to
|
|
/// forward immediately.
|
|
factory StringConversionSink.fromStringSink(StringSink sink) =
|
|
_StringSinkConversionSink<StringSink>;
|
|
|
|
/// Adds the next [chunk] to `this`.
|
|
///
|
|
/// Adds the substring defined by [start] and [end]-exclusive to `this`.
|
|
///
|
|
/// If [isLast] is `true` closes `this`.
|
|
void addSlice(String chunk, int start, int end, bool isLast);
|
|
|
|
/// Returns `this` as a sink that accepts UTF-8 input.
|
|
///
|
|
/// If used, this method must be the first and only call to `this`. It
|
|
/// invalidates `this`. All further operations must be performed on the result.
|
|
ByteConversionSink asUtf8Sink(bool allowMalformed);
|
|
// - asRuneSink
|
|
// - asCodeUnitsSink
|
|
|
|
/// Returns `this` as a [ClosableStringSink].
|
|
///
|
|
/// If used, this method must be the first and only call to `this`. It
|
|
/// invalidates `this`. All further operations must be performed on the result.
|
|
ClosableStringSink asStringSink();
|
|
}
|
|
|
|
/// A [ClosableStringSink] extends the [StringSink] interface by adding a
|
|
/// `close` method.
|
|
abstract class ClosableStringSink extends StringSink {
|
|
/// Creates a new instance combining a [StringSink] [sink] and a callback
|
|
/// [onClose] which is invoked when the returned instance is closed.
|
|
factory ClosableStringSink.fromStringSink(StringSink sink, void onClose()) =
|
|
_ClosableStringSink;
|
|
|
|
/// Closes `this` and flushes any outstanding data.
|
|
void close();
|
|
}
|
|
|
|
/// This class wraps an existing [StringSink] and invokes a
|
|
/// closure when [close] is invoked.
|
|
class _ClosableStringSink implements ClosableStringSink {
|
|
final void Function() _callback;
|
|
final StringSink _sink;
|
|
|
|
_ClosableStringSink(this._sink, this._callback);
|
|
|
|
void close() {
|
|
_callback();
|
|
}
|
|
|
|
void writeCharCode(int charCode) {
|
|
_sink.writeCharCode(charCode);
|
|
}
|
|
|
|
void write(Object? o) {
|
|
_sink.write(o);
|
|
}
|
|
|
|
void writeln([Object? o = ""]) {
|
|
_sink.writeln(o);
|
|
}
|
|
|
|
void writeAll(Iterable objects, [String separator = ""]) {
|
|
_sink.writeAll(objects, separator);
|
|
}
|
|
}
|
|
|
|
/// This class wraps an existing [StringConversionSink] and exposes a
|
|
/// [ClosableStringSink] interface. The wrapped sink only needs to implement
|
|
/// `add` and `close`.
|
|
// TODO(floitsch): make this class public?
|
|
class _StringConversionSinkAsStringSinkAdapter implements ClosableStringSink {
|
|
static const _MIN_STRING_SIZE = 16;
|
|
|
|
final StringBuffer _buffer;
|
|
final StringConversionSink _chunkedSink;
|
|
|
|
_StringConversionSinkAsStringSinkAdapter(this._chunkedSink)
|
|
: _buffer = StringBuffer();
|
|
|
|
void close() {
|
|
if (_buffer.isNotEmpty) _flush();
|
|
_chunkedSink.close();
|
|
}
|
|
|
|
void writeCharCode(int charCode) {
|
|
_buffer.writeCharCode(charCode);
|
|
if (_buffer.length > _MIN_STRING_SIZE) _flush();
|
|
}
|
|
|
|
void write(Object? o) {
|
|
if (_buffer.isNotEmpty) _flush();
|
|
_chunkedSink.add(o.toString());
|
|
}
|
|
|
|
void writeln([Object? o = ""]) {
|
|
_buffer.writeln(o);
|
|
if (_buffer.length > _MIN_STRING_SIZE) _flush();
|
|
}
|
|
|
|
void writeAll(Iterable objects, [String separator = ""]) {
|
|
if (_buffer.isNotEmpty) _flush();
|
|
var iterator = objects.iterator;
|
|
if (!iterator.moveNext()) return;
|
|
if (separator.isEmpty) {
|
|
do {
|
|
_chunkedSink.add(iterator.current.toString());
|
|
} while (iterator.moveNext());
|
|
} else {
|
|
_chunkedSink.add(iterator.current.toString());
|
|
while (iterator.moveNext()) {
|
|
write(separator);
|
|
_chunkedSink.add(iterator.current.toString());
|
|
}
|
|
}
|
|
}
|
|
|
|
void _flush() {
|
|
var accumulated = _buffer.toString();
|
|
_buffer.clear();
|
|
_chunkedSink.add(accumulated);
|
|
}
|
|
}
|
|
|
|
/// This class provides a base-class for converters that need to accept String
|
|
/// inputs.
|
|
abstract class StringConversionSinkBase extends StringConversionSinkMixin {}
|
|
|
|
/// This class provides a mixin for converters that need to accept String
|
|
/// inputs.
|
|
abstract class StringConversionSinkMixin implements StringConversionSink {
|
|
void addSlice(String str, int start, int end, bool isLast);
|
|
void close();
|
|
|
|
void add(String str) {
|
|
addSlice(str, 0, str.length, false);
|
|
}
|
|
|
|
ByteConversionSink asUtf8Sink(bool allowMalformed) {
|
|
return _Utf8ConversionSink(this, allowMalformed);
|
|
}
|
|
|
|
ClosableStringSink asStringSink() {
|
|
return _StringConversionSinkAsStringSinkAdapter(this);
|
|
}
|
|
}
|
|
|
|
/// This class is a [StringConversionSink] that wraps a [StringSink].
|
|
class _StringSinkConversionSink<TStringSink extends StringSink>
|
|
extends StringConversionSinkBase {
|
|
final TStringSink _stringSink;
|
|
_StringSinkConversionSink(this._stringSink);
|
|
|
|
void close() {}
|
|
|
|
void addSlice(String str, int start, int end, bool isLast) {
|
|
if (start != 0 || end != str.length) {
|
|
for (var i = start; i < end; i++) {
|
|
_stringSink.writeCharCode(str.codeUnitAt(i));
|
|
}
|
|
} else {
|
|
_stringSink.write(str);
|
|
}
|
|
if (isLast) close();
|
|
}
|
|
|
|
void add(String str) {
|
|
_stringSink.write(str);
|
|
}
|
|
|
|
ByteConversionSink asUtf8Sink(bool allowMalformed) {
|
|
return _Utf8StringSinkAdapter(this, _stringSink, allowMalformed);
|
|
}
|
|
|
|
ClosableStringSink asStringSink() {
|
|
return ClosableStringSink.fromStringSink(_stringSink, close);
|
|
}
|
|
}
|
|
|
|
/// This class accumulates all chunks into one string
|
|
/// and invokes a callback when the sink is closed.
|
|
///
|
|
/// This class can be used to terminate a chunked conversion.
|
|
class _StringCallbackSink extends _StringSinkConversionSink<StringBuffer> {
|
|
final void Function(String) _callback;
|
|
|
|
_StringCallbackSink(this._callback) : super(StringBuffer());
|
|
|
|
void close() {
|
|
var accumulated = _stringSink.toString();
|
|
_stringSink.clear();
|
|
_callback(accumulated);
|
|
}
|
|
|
|
ByteConversionSink asUtf8Sink(bool allowMalformed) {
|
|
return _Utf8StringSinkAdapter(this, _stringSink, allowMalformed);
|
|
}
|
|
}
|
|
|
|
/// This class adapts a simple [ChunkedConversionSink] to a
|
|
/// [StringConversionSink].
|
|
///
|
|
/// All additional methods of the [StringConversionSink] (compared to the
|
|
/// ChunkedConversionSink) are redirected to the `add` method.
|
|
class _StringAdapterSink extends StringConversionSinkBase {
|
|
final Sink<String> _sink;
|
|
|
|
_StringAdapterSink(this._sink);
|
|
|
|
void add(String str) {
|
|
_sink.add(str);
|
|
}
|
|
|
|
void addSlice(String str, int start, int end, bool isLast) {
|
|
if (start == 0 && end == str.length) {
|
|
add(str);
|
|
} else {
|
|
add(str.substring(start, end));
|
|
}
|
|
if (isLast) close();
|
|
}
|
|
|
|
void close() {
|
|
_sink.close();
|
|
}
|
|
}
|
|
|
|
/// Decodes UTF-8 code units and stores them in a [StringSink].
|
|
///
|
|
/// The `Sink` provided is closed when this sink is closed.
|
|
class _Utf8StringSinkAdapter extends ByteConversionSink {
|
|
final _Utf8Decoder _decoder;
|
|
final Sink<Object?> _sink;
|
|
|
|
_Utf8StringSinkAdapter(this._sink, StringSink stringSink, bool allowMalformed)
|
|
: _decoder = _Utf8Decoder(stringSink, allowMalformed);
|
|
|
|
void close() {
|
|
_decoder.close();
|
|
_sink.close();
|
|
}
|
|
|
|
void add(List<int> chunk) {
|
|
addSlice(chunk, 0, chunk.length, false);
|
|
}
|
|
|
|
void addSlice(
|
|
List<int> codeUnits, int startIndex, int endIndex, bool isLast) {
|
|
_decoder.convert(codeUnits, startIndex, endIndex);
|
|
if (isLast) close();
|
|
}
|
|
}
|
|
|
|
/// Decodes UTF-8 code units.
|
|
///
|
|
/// Forwards the decoded strings to the given [StringConversionSink].
|
|
// TODO(floitsch): make this class public?
|
|
class _Utf8ConversionSink extends ByteConversionSink {
|
|
final _Utf8Decoder _decoder;
|
|
final StringConversionSink _chunkedSink;
|
|
final StringBuffer _buffer;
|
|
_Utf8ConversionSink(StringConversionSink sink, bool allowMalformed)
|
|
: this._(sink, StringBuffer(), allowMalformed);
|
|
|
|
_Utf8ConversionSink._(
|
|
this._chunkedSink, StringBuffer stringBuffer, bool allowMalformed)
|
|
: _decoder = _Utf8Decoder(stringBuffer, allowMalformed),
|
|
_buffer = stringBuffer;
|
|
|
|
void close() {
|
|
_decoder.close();
|
|
if (_buffer.isNotEmpty) {
|
|
var accumulated = _buffer.toString();
|
|
_buffer.clear();
|
|
_chunkedSink.addSlice(accumulated, 0, accumulated.length, true);
|
|
} else {
|
|
_chunkedSink.close();
|
|
}
|
|
}
|
|
|
|
void add(List<int> chunk) {
|
|
addSlice(chunk, 0, chunk.length, false);
|
|
}
|
|
|
|
void addSlice(List<int> chunk, int startIndex, int endIndex, bool isLast) {
|
|
_decoder.convert(chunk, startIndex, endIndex);
|
|
if (_buffer.isNotEmpty) {
|
|
var accumulated = _buffer.toString();
|
|
_chunkedSink.addSlice(accumulated, 0, accumulated.length, isLast);
|
|
_buffer.clear();
|
|
return;
|
|
}
|
|
if (isLast) close();
|
|
}
|
|
}
|