|  | // 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(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(); | 
|  | } | 
|  | } |