blob: 6eb27d992c3f5be05351f9c6d059166ac513fdd0 [file] [log] [blame]
// 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;
/**
* 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();
}
typedef void _StringSinkCloseCallback();
/**
* This class wraps an existing [StringSink] and invokes a
* closure when [close] is invoked.
*/
class _ClosableStringSink implements ClosableStringSink {
final _StringSinkCloseCallback _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;
StringBuffer _buffer;
StringConversionSink _chunkedSink;
_StringConversionSinkAsStringSinkAdapter(this._chunkedSink)
: _buffer = new 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();
Iterator 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() {
String 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 new _Utf8ConversionSink(this, allowMalformed);
}
ClosableStringSink asStringSink() {
return new _StringConversionSinkAsStringSinkAdapter(this);
}
}
/**
* This class is a [StringConversionSink] that wraps a [StringSink].
*/
class _StringSinkConversionSink extends StringConversionSinkBase {
StringSink _stringSink;
_StringSinkConversionSink(StringSink this._stringSink);
void close() {}
void addSlice(String str, int start, int end, bool isLast) {
if (start != 0 || end != str.length) {
for (int 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 new _Utf8StringSinkAdapter(this, _stringSink, allowMalformed);
}
ClosableStringSink asStringSink() {
return new ClosableStringSink.fromStringSink(_stringSink, this.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 {
final _ChunkedConversionCallback<String> _callback;
_StringCallbackSink(this._callback) : super(new StringBuffer());
void close() {
StringBuffer buffer = _stringSink;
String accumulated = buffer.toString();
buffer.clear();
_callback(accumulated);
}
ByteConversionSink asUtf8Sink(bool allowMalformed) {
return new _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].
*/
class _Utf8StringSinkAdapter extends ByteConversionSink {
final _Utf8Decoder _decoder;
final Sink _sink;
_Utf8StringSinkAdapter(this._sink,
StringSink stringSink, bool allowMalformed)
: _decoder = new _Utf8Decoder(stringSink, allowMalformed);
void close() {
_decoder.close();
if(_sink != null) _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, new StringBuffer(), allowMalformed);
_Utf8ConversionSink._(this._chunkedSink, StringBuffer stringBuffer,
bool allowMalformed)
: _decoder = new _Utf8Decoder(stringBuffer, allowMalformed),
_buffer = stringBuffer;
void close() {
_decoder.close();
if (_buffer.isNotEmpty) {
String 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) {
String accumulated = _buffer.toString();
_chunkedSink.addSlice(accumulated, 0, accumulated.length, isLast);
_buffer.clear();
return;
}
if (isLast) close();
}
}