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