blob: d450c75c06d36f0bef9daaa248e040042de97735 [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;
typedef void _ChunkedConversionCallback<T>(T accumulated);
/**
* A converter that supports chunked conversions.
*
* In addition to immediate conversions from [S] to [T], a chunked converter
* also supports longer-running conversions from [S2] to [T2].
*
* Frequently, the source and target types are the same, but this is not a
* requirement. In particular, converters that work with lists in the
* immediate conversion, could flatten the type for the chunked conversion.
*
* For example, the [LineSplitter] class returns a `List<String>` for the
* immediate conversion, but returns individual `String`s in the chunked
* conversion.
*/
abstract class ChunkedConverter<S, T, S2, T2> extends Converter<S, T> {
const ChunkedConverter();
/**
* Starts a chunked conversion.
*
* The returned sink serves as input for the long-running conversion. The
* given [sink] serves as output.
*/
ChunkedConversionSink<S2> startChunkedConversion(Sink<T2> sink) {
throw new UnsupportedError(
"This converter does not support chunked conversions: $this");
}
Stream<T2> bind(Stream<S2> stream) {
return new Stream<T2>.eventTransformed(
stream,
(EventSink<T2> sink) =>
new _ConverterStreamEventSink<S2, T2>(this, sink));
}
/**
* Fuses this instance with the given [other] converter.
*
* If [other] is a ChunkedConverter (with matching generic types), returns a
* [ChunkedConverter].
*/
Converter<S, dynamic> fuse(Converter<T, dynamic> other) {
if (other is ChunkedConverter<T, dynamic, T2, dynamic>) {
return new _FusedChunkedConverter<S, T, dynamic, S2, T2, dynamic>(
this, other);
}
return super.fuse(other);
}
}
/**
* A [ChunkedConversionSink] is used to transmit data more efficiently between
* two converters during chunked conversions.
*
* The basic `ChunkedConversionSink` is just a [Sink], and converters should
* work with a plain `Sink`, but may work more efficiently with certain
* specialized types of `ChunkedConversionSink`.
*
* It is recommended that implementations of `ChunkedConversionSink` extends
* this class, to inherit any further methods that may be added to the class.
*/
abstract class ChunkedConversionSink<T> implements Sink<T> {
ChunkedConversionSink();
factory ChunkedConversionSink.withCallback(
void callback(List<T> accumulated)) = _SimpleCallbackSink;
/**
* Adds chunked data to this sink.
*
* This method is also used when converters are used as [StreamTransformer]s.
*/
void add(T chunk);
/**
* Closes the sink.
*
* This signals the end of the chunked conversion. This method is called
* when converters are used as [StreamTransformer]'s.
*/
void close();
}
/**
* This class accumulates all chunks and invokes a callback with a list of
* the chunks when the sink is closed.
*
* This class can be used to terminate a chunked conversion.
*/
class _SimpleCallbackSink<T> extends ChunkedConversionSink<T> {
final _ChunkedConversionCallback<List<T>> _callback;
final List<T> _accumulated = <T>[];
_SimpleCallbackSink(this._callback);
void add(T chunk) { _accumulated.add(chunk); }
void close() { _callback(_accumulated); }
}
/**
* This class implements the logic for a chunked conversion as a
* stream transformer.
*
* It is used as strategy in the [EventTransformStream].
*
* It also implements the [ChunkedConversionSink] interface so that it
* can be used as output sink in a chunked conversion.
*/
class _ConverterStreamEventSink<S, T> implements EventSink<S> {
/** The output sink for the converter. */
final EventSink<T> _eventSink;
/**
* The input sink for new data. All data that is received with
* [handleData] is added into this sink.
*/
ChunkedConversionSink<S> _chunkedSink;
_ConverterStreamEventSink(
Converter/*=ChunkedConverter<dynamic, dynamic, S, T>*/ converter,
EventSink<T> sink)
: this._eventSink = sink,
_chunkedSink = converter.startChunkedConversion(sink);
void add(S o) => _chunkedSink.add(o);
void addError(Object error, [StackTrace stackTrace]) {
_eventSink.addError(error, stackTrace);
}
void close() => _chunkedSink.close();
}
/**
* Fuses two chunked converters.
*/
class _FusedChunkedConverter<S, M, T, S2, M2, T2> extends
ChunkedConverter<S, T, S2, T2> {
final ChunkedConverter<S, M, S2, M2> _first;
final ChunkedConverter<M, T, M2, T2> _second;
_FusedChunkedConverter(this._first, this._second);
T convert(S input) => _second.convert(_first.convert(input));
ChunkedConversionSink<S2> startChunkedConversion(Sink<T2> sink) {
return _first.startChunkedConversion(_second.startChunkedConversion(sink));
}
}