| // 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; |
| final StringSink _stringSink; |
| |
| _Utf8StringSinkAdapter(this._sink, this._stringSink, bool allowMalformed) |
| : _decoder = _Utf8Decoder(allowMalformed); |
| |
| void close() { |
| _decoder.flush(_stringSink); |
| _sink.close(); |
| } |
| |
| void add(List<int> chunk) { |
| addSlice(chunk, 0, chunk.length, false); |
| } |
| |
| void addSlice( |
| List<int> codeUnits, int startIndex, int endIndex, bool isLast) { |
| _stringSink.write(_decoder.convertChunked(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(allowMalformed), |
| _buffer = stringBuffer; |
| |
| void close() { |
| _decoder.flush(_buffer); |
| 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) { |
| _buffer.write(_decoder.convertChunked(chunk, startIndex, endIndex)); |
| if (_buffer.isNotEmpty) { |
| var accumulated = _buffer.toString(); |
| _chunkedSink.addSlice(accumulated, 0, accumulated.length, isLast); |
| _buffer.clear(); |
| return; |
| } |
| if (isLast) close(); |
| } |
| } |