Chromium Code Reviews| Index: sdk/lib/convert/string_conversion.dart |
| diff --git a/sdk/lib/convert/string_conversion.dart b/sdk/lib/convert/string_conversion.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..72138935e2b901ef7d36f7a78607ee64b8941327 |
| --- /dev/null |
| +++ b/sdk/lib/convert/string_conversion.dart |
| @@ -0,0 +1,372 @@ |
| +// 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 represents the [StringConversionSink] interface. It is |
| + * accessed through [StringConversionSink.INTERFACE] or as instance field |
| + * `interface` from [StringConversionSink]s. |
| + */ |
| +class _StringInterface extends ChunkedConversionInterface { |
| + const _StringInterface(); |
| + |
| + StringConversionSink adapt(ChunkedConversionSink sink) { |
| + if (sink.interface == StringConversionSink.INTERFACE) return sink; |
| + return new _StringAdapterSink(sink); |
| + } |
| + |
| + /** |
| + * Creates a [StringConversionSink] with a callback. |
| + * |
| + * The [callback] is invoked with the accumulated data when the sink is |
| + * closed. |
| + */ |
| + StringConversionSink createSink(void callback(String accumulated)) { |
| + return new _StringCallbackSink(callback); |
| + } |
| +} |
| + |
| +/** |
| + * 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 and UTF-8 code units. |
|
Søren Gjesse
2013/07/25 08:07:00
The comment here is a bit misleading. It says "acc
floitsch
2013/07/25 12:57:24
Rewritten.
|
| + */ |
| +abstract class StringConversionSink |
| + extends ChunkedConversionSink<String> { |
| + |
| + static const ChunkedConversionInterface INTERFACE = const _StringInterface(); |
| + |
| + StringConversionSink(); |
| + |
| + /** |
| + * 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. |
| + * |
| + * This method must be the first and only call to `this`. It invalidates |
|
Søren Gjesse
2013/07/25 08:07:00
Prefix "This meth..." with "If used".
floitsch
2013/07/25 12:57:24
Done.
|
| + * `this`. All further operations must be performed on the result. |
| + */ |
| + ByteConversionSink asUtf8Sink(bool allowMalformed); |
| + // - asRuneSink |
| + // - asCodeUnitsSink |
| + |
| + /** |
| + * Returns `this` as a [ClosableStringSink]. |
| + * |
| + * This method must be the first and only call to `this`. It invalidates |
|
Søren Gjesse
2013/07/25 08:07:00
Ditto.
floitsch
2013/07/25 12:57:24
Done.
|
| + * `this`. All further operations must be performed on the result. |
| + */ |
| + ClosableStringSink asStringSink(); |
| + |
| + ChunkedConversionInterface get interface => INTERFACE; |
| +} |
| + |
| +/** |
| + * 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(); |
| + String str = o.toString(); |
| + _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); |
| + } |
| + |
| + ChunkedConversionInterface get interface => StringConversionSink.INTERFACE; |
| + |
| + ClosableStringSink asStringSink() { |
| + return new _StringConversionSinkAsStringSinkAdapter(this); |
| + } |
| + |
| + ChunkedConversionSink adaptTo(ChunkedConversionInterface interface) { |
| + if (this.interface == interface) return this; |
| + return interface.adapt(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 ChunkedConversionSink<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 ChunkedConversionSink _chunkedSink; |
| + |
| + _Utf8StringSinkAdapter(ChunkedConversionSink chunkedSink, |
| + StringSink sink, bool allowMalformed) |
| + : _chunkedSink = chunkedSink, |
| + _decoder = new _Utf8Decoder(sink, allowMalformed); |
| + |
| + void close() { |
| + _decoder.close(); |
| + if(_chunkedSink != null) _chunkedSink.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 { |
| + static const _MIN_STRING_SIZE = 16; |
| + |
| + 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.length > _MIN_STRING_SIZE) { |
| + String accumulated = _buffer.toString(); |
| + _chunkedSink.addSlice(accumulated, 0, accumulated.length, isLast); |
| + _buffer.clear(); |
| + return; |
| + } |
| + if (isLast) close(); |
| + } |
| +} |