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..ee0d135e53a17756f1d4b75fed141aa6bcaf7d6c |
--- /dev/null |
+++ b/sdk/lib/convert/string_conversion.dart |
@@ -0,0 +1,341 @@ |
+// 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. |
+ */ |
+abstract class StringConversionSink |
+ extends ChunkedConversionSink<String> { |
+ StringConversionSink(); |
+ factory StringConversionSink.withCallback(void callback(String accumulated)) |
+ = _StringCallbackSink; |
+ factory StringConversionSink.from(ChunkedConversionSink<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(); |
+ 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); |
+ } |
+ |
+ 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 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(); |
+ } |
+} |